Compare commits
	
		
			8 Commits
		
	
	
		
			net_prof
			...
			bb510b685a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | bb510b685a | ||
|  | 876f482391 | ||
|  | 42dd6d16d7 | ||
|  | 11ae259f67 | ||
|  | f2027befc8 | ||
|  | 9777cb81cb | ||
|  | 84ade4d683 | ||
|  | f89aeae62b | 
							
								
								
									
										2
									
								
								external/solanaceae_object_store
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								external/solanaceae_object_store
									
									
									
									
										vendored
									
									
								
							 Submodule external/solanaceae_object_store updated: ed640ba08c...18d2888e34
									
								
							
							
								
								
									
										2
									
								
								external/solanaceae_util
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								external/solanaceae_util
									
									
									
									
										vendored
									
									
								
							 Submodule external/solanaceae_util updated: 717748e8fc...85bbbb0e5a
									
								
							| @@ -56,6 +56,8 @@ target_sources(tomato PUBLIC | ||||
| 	./tox_avatar_loader.cpp | ||||
| 	./message_image_loader.hpp | ||||
| 	./message_image_loader.cpp | ||||
| 	./bitset_image_loader.hpp | ||||
| 	./bitset_image_loader.cpp | ||||
|  | ||||
| 	./tox_avatar_manager.hpp | ||||
| 	./tox_avatar_manager.cpp | ||||
|   | ||||
							
								
								
									
										153
									
								
								src/bitset_image_loader.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/bitset_image_loader.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | ||||
| #include "./bitset_image_loader.hpp" | ||||
|  | ||||
| #include <solanaceae/object_store/object_store.hpp> | ||||
| #include <solanaceae/object_store/meta_components_file.hpp> | ||||
|  | ||||
| #include "./os_comps.hpp" | ||||
|  | ||||
| #include <entt/entity/entity.hpp> | ||||
|  | ||||
| #include <SDL3/SDL.h> | ||||
|  | ||||
| #include <iostream> | ||||
|  | ||||
| // fwd | ||||
| namespace Message { | ||||
| uint64_t getTimeMS(void); | ||||
| } | ||||
|  | ||||
| std::optional<TextureEntry> BitsetImageLoader::haveToTexture(TextureUploaderI& tu, BitSet& have, ObjectHandle o) { | ||||
| 	assert(have.size_bits() > 0); | ||||
|  | ||||
| 	auto* surf = SDL_CreateSurfaceFrom( | ||||
| 		have.size_bits(), 1, | ||||
| 		SDL_PIXELFORMAT_INDEX1MSB, // LSB ? | ||||
| 		have.data(), have.size_bytes() | ||||
| 	); | ||||
| 	if (surf == nullptr) { | ||||
| 		std::cerr << "BIL error: bitset to 1bit surface creationg failed o:" << entt::to_integral(o.entity()) << "\n"; | ||||
| 		return std::nullopt; | ||||
| 	} | ||||
|  | ||||
| 	SDL_Color colors[] { | ||||
| 		{0, 0, 0, 0}, | ||||
| 		{255, 255, 255, 255}, | ||||
| 	}; | ||||
|  | ||||
| 	SDL_Palette* palette = SDL_CreatePalette(2); | ||||
| 	SDL_SetPaletteColors(palette, colors, 0, 2); | ||||
| 	SDL_SetSurfacePalette(surf, palette); | ||||
| 	auto* conv_surf = SDL_ConvertSurface(surf, SDL_PIXELFORMAT_RGBA32); | ||||
|  | ||||
| 	SDL_DestroySurface(surf); | ||||
| 	SDL_DestroyPalette(palette); | ||||
|  | ||||
| 	if (conv_surf == nullptr) { | ||||
| 		std::cerr << "BIL error: surface conversion failed o:" << entt::to_integral(o.entity()) << " : " << SDL_GetError() << "\n"; | ||||
| 		return std::nullopt; | ||||
| 	} | ||||
|  | ||||
| 	SDL_LockSurface(conv_surf); | ||||
|  | ||||
| 	TextureEntry new_entry; | ||||
| 	new_entry.timestamp_last_rendered = Message::getTimeMS(); | ||||
| 	new_entry.width = have.size_bits(); | ||||
| 	new_entry.height = 1; | ||||
|  | ||||
| 	const auto n_t = tu.upload(static_cast<uint8_t*>(conv_surf->pixels), conv_surf->w, conv_surf->h, TextureUploaderI::RGBA); | ||||
| 	assert(n_t != 0); | ||||
| 	new_entry.textures.push_back(n_t); | ||||
| 	new_entry.frame_duration.push_back(1); | ||||
|  | ||||
| 	std::cout << "BIL: genereated bitset image o:" << entt::to_integral(o.entity()) << "\n"; | ||||
|  | ||||
| 	SDL_UnlockSurface(conv_surf); | ||||
| 	SDL_DestroySurface(conv_surf); | ||||
|  | ||||
| 	return new_entry; | ||||
| } | ||||
|  | ||||
| BitsetImageLoader::BitsetImageLoader(void) { | ||||
| } | ||||
|  | ||||
| TextureLoaderResult BitsetImageLoader::load(TextureUploaderI& tu, ObjectHandle o) { | ||||
| 	if (!static_cast<bool>(o)) { | ||||
| 		std::cerr << "BIL error: trying to load invalid object\n"; | ||||
| 		return {}; | ||||
| 	} | ||||
|  | ||||
| 	if (!o.any_of<ObjComp::F::LocalHaveBitset, ObjComp::F::RemoteHaveBitset>()) { | ||||
| 		// after completion, this is called until the texture times out | ||||
| 		//std::cout << "BIL: no have bitset\n"; | ||||
| 		return {}; | ||||
| 	} | ||||
|  | ||||
| 	if (o.all_of<ObjComp::F::LocalHaveBitset>()) { | ||||
| 		auto& have = o.get<ObjComp::F::LocalHaveBitset>().have; | ||||
| 		assert(have.size_bits() > 0); | ||||
| 		return {haveToTexture(tu, have, o)}; | ||||
| 	} else if (o.all_of<ObjComp::F::RemoteHaveBitset>()) { | ||||
| 		auto& list = o.get<ObjComp::F::RemoteHaveBitset>().others; | ||||
| 		if (list.empty()) { | ||||
| 			std::cout << "BIL: remote set list empty\n"; | ||||
| 			_tmp_bitset = {8}; | ||||
| 			return {haveToTexture(tu, _tmp_bitset, o)}; | ||||
| 		} | ||||
| 		const auto& first_entry = list.begin()->second; | ||||
|  | ||||
| 		if (first_entry.have_all) { | ||||
| 			_tmp_bitset = {8}; | ||||
| 			_tmp_bitset.invert(); | ||||
| 			std::cout << "BIL: remote first have all\n"; | ||||
| 		} else { | ||||
| 			_tmp_bitset = first_entry.have; | ||||
| 			assert(_tmp_bitset.size_bits() == first_entry.have.size_bits()); | ||||
|  | ||||
| 			for (auto it = list.begin()+1; it != list.end(); it++) { | ||||
| 				if (it->second.have_all) { | ||||
| 					_tmp_bitset = {8}; | ||||
| 					_tmp_bitset.invert(); | ||||
| 					std::cout << "BIL: remote have all\n"; | ||||
| 					break; | ||||
| 				} | ||||
|  | ||||
| 				_tmp_bitset.merge(it->second.have); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return {haveToTexture(tu, _tmp_bitset, o)}; | ||||
| 	} | ||||
|  | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| std::optional<TextureEntry> BitsetImageLoader::load(TextureUploaderI& tu, ObjectContactSub ocs) { | ||||
| 	if (!static_cast<bool>(ocs.o)) { | ||||
| 		std::cerr << "BIL error: trying to load invalid object\n"; | ||||
| 		return std::nullopt; | ||||
| 	} | ||||
|  | ||||
| 	if (!ocs.o.all_of<ObjComp::F::RemoteHaveBitset>()) { | ||||
| 		// after completion, this is called until the texture times out | ||||
| 		return std::nullopt; | ||||
| 	} | ||||
|  | ||||
| 	auto& map = ocs.o.get<ObjComp::F::RemoteHaveBitset>().others; | ||||
| 	auto it = map.find(ocs.c); | ||||
| 	if (it == map.end()) { | ||||
| 		// contact not found | ||||
| 		return std::nullopt; | ||||
| 	} | ||||
|  | ||||
| 	if (it->second.have_all) { | ||||
| 		BitSet tmp{8}; // or 1? | ||||
| 		tmp.invert(); | ||||
| 		return haveToTexture(tu, tmp, ocs.o); | ||||
| 	} else if (it->second.have.size_bits() == 0) { | ||||
| 		BitSet tmp{8}; // or 1? | ||||
| 		return haveToTexture(tu, tmp, ocs.o); | ||||
| 	} else { | ||||
| 		return haveToTexture(tu, it->second.have, ocs.o); | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										36
									
								
								src/bitset_image_loader.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/bitset_image_loader.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <solanaceae/object_store/fwd.hpp> | ||||
| #include <solanaceae/contact/contact_model3.hpp> | ||||
| #include <solanaceae/util/bitset.hpp> | ||||
|  | ||||
| #include "./texture_cache.hpp" | ||||
|  | ||||
| #include <optional> | ||||
|  | ||||
| struct ObjectContactSub final { | ||||
| 	ObjectHandle o; | ||||
| 	Contact3 c{entt::null}; | ||||
| }; | ||||
|  | ||||
| template<> | ||||
| struct std::hash<ObjectContactSub> { | ||||
| 	std::size_t operator()(ObjectContactSub const& ocs) const noexcept { | ||||
| 		const std::size_t h1 = reinterpret_cast<std::size_t>(ocs.o.registry()); | ||||
| 		const std::size_t h2 = entt::to_integral(ocs.o.entity()); | ||||
| 		const std::size_t h3 = entt::to_integral(ocs.c); | ||||
| 		return (h1 << 3) ^ (h3 << 7) ^ (h2 * 11400714819323198485llu); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| class BitsetImageLoader { | ||||
| 	BitSet _tmp_bitset; | ||||
|  | ||||
| 	std::optional<TextureEntry> haveToTexture(TextureUploaderI& tu, BitSet& have, ObjectHandle o); | ||||
|  | ||||
| 	public: | ||||
| 		BitsetImageLoader(void); | ||||
| 		TextureLoaderResult load(TextureUploaderI& tu, ObjectHandle o); | ||||
| 		std::optional<TextureEntry> load(TextureUploaderI& tu, ObjectContactSub ocs); | ||||
| }; | ||||
|  | ||||
| @@ -11,6 +11,8 @@ | ||||
|  | ||||
| #include <imgui/imgui.h> | ||||
|  | ||||
| #include <cmath> | ||||
|  | ||||
| // fwd | ||||
| namespace Message { | ||||
| uint64_t getTimeMS(void); | ||||
| @@ -91,7 +93,7 @@ bool SendImagePopup::load(void) { | ||||
| 		preview_image.timestamp_last_rendered = Message::getTimeMS(); | ||||
| 		preview_image.current_texture = 0; | ||||
| 		for (const auto& [ms, data] : original_image.frames) { | ||||
| 			const auto n_t = _tu.uploadRGBA(data.data(), original_image.width, original_image.height); | ||||
| 			const auto n_t = _tu.upload(data.data(), original_image.width, original_image.height); | ||||
| 			preview_image.textures.push_back(n_t); | ||||
| 			preview_image.frame_duration.push_back(ms); | ||||
| 		} | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| #include "./chat_gui4.hpp" | ||||
|  | ||||
| #include <solanaceae/object_store/object_store.hpp> | ||||
|  | ||||
| #include <solanaceae/message3/components.hpp> | ||||
| #include <solanaceae/tox_messages/msg_components.hpp> | ||||
| #include <solanaceae/tox_messages/obj_components.hpp> | ||||
| @@ -18,6 +16,7 @@ | ||||
|  | ||||
| #include <imgui/imgui.h> | ||||
| #include <imgui/misc/cpp/imgui_stdlib.h> | ||||
| #include <imgui/imgui_internal.h> | ||||
|  | ||||
| #include <SDL3/SDL.h> | ||||
|  | ||||
| @@ -25,6 +24,7 @@ | ||||
|  | ||||
| #include "./media_meta_info_loader.hpp" | ||||
| #include "./sdl_clipboard_utils.hpp" | ||||
| #include "os_comps.hpp" | ||||
|  | ||||
| #include <cctype> | ||||
| #include <ctime> | ||||
| @@ -246,7 +246,19 @@ ChatGui4::ChatGui4( | ||||
| 	ContactTextureCache& contact_tc, | ||||
| 	MessageTextureCache& msg_tc, | ||||
| 	Theme& theme | ||||
| ) : _conf(conf), _os(os), _rmm(rmm), _cr(cr), _contact_tc(contact_tc), _msg_tc(msg_tc), _theme(theme), _sip(tu) { | ||||
| ) : | ||||
| 	_conf(conf), | ||||
| 	_os(os), | ||||
| 	_os_sr(_os.newSubRef(this)), | ||||
| 	_rmm(rmm), | ||||
| 	_cr(cr), | ||||
| 	_contact_tc(contact_tc), | ||||
| 	_msg_tc(msg_tc), | ||||
| 	_b_tc(_bil, tu), | ||||
| 	_theme(theme), | ||||
| 	_sip(tu) | ||||
| { | ||||
| 	_os_sr.subscribe(ObjectStore_Event::object_update); | ||||
| } | ||||
|  | ||||
| ChatGui4::~ChatGui4(void) { | ||||
| @@ -263,6 +275,8 @@ ChatGui4::~ChatGui4(void) { | ||||
| float ChatGui4::render(float time_delta) { | ||||
| 	_fss.render(); | ||||
| 	_sip.render(time_delta); | ||||
| 	_b_tc.update(); | ||||
| 	_b_tc.workLoadQueue(); | ||||
|  | ||||
| 	const ImGuiViewport* viewport = ImGui::GetMainViewport(); | ||||
| 	ImGui::SetNextWindowPos(viewport->WorkPos); | ||||
| @@ -1030,7 +1044,7 @@ void ChatGui4::renderMessageBodyText(Message3Registry& reg, const Message3 e) { | ||||
| 	ImGui::BeginGroup(); | ||||
| 	do { | ||||
| 		const auto current_line = msgtext_sv.substr(pos_prev, pos_next - pos_prev); | ||||
| 		if (current_line.front() == '>') { | ||||
| 		if (!current_line.empty() && current_line.front() == '>') { | ||||
| 			// TODO: theming | ||||
| 			ImGui::PushStyleColor(ImGuiCol_Text, {0.3f, 0.9f, 0.1f, 1.f}); | ||||
| 			ImGui::TextUnformatted(current_line.data(), current_line.data()+current_line.size()); | ||||
| @@ -1123,6 +1137,10 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { | ||||
| 		// TODO: missing other states | ||||
| 		ImGui::TextUnformatted("running"); | ||||
| 	} | ||||
| 	if (o.all_of<ObjComp::F::TagLocalHaveAll>()) { | ||||
| 		ImGui::SameLine(); | ||||
| 		ImGui::TextUnformatted("(have all)"); | ||||
| 	} | ||||
|  | ||||
| 	// if in offered state | ||||
| 	// paused, never started | ||||
| @@ -1158,6 +1176,8 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { | ||||
| 	// hacky | ||||
| 	const auto* fts = o.try_get<ObjComp::Ephemeral::File::TransferStats>(); | ||||
| 	if (fts != nullptr && o.any_of<ObjComp::F::SingleInfo, ObjComp::F::CollectionInfo>()) { | ||||
| 		const bool upload = o.all_of<ObjComp::F::TagLocalHaveAll>() && fts->total_down <= 0; | ||||
|  | ||||
| 		const int64_t total_size = | ||||
| 			o.all_of<ObjComp::F::SingleInfo>() ? | ||||
| 				o.get<ObjComp::F::SingleInfo>().file_size : | ||||
| @@ -1166,7 +1186,7 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { | ||||
|  | ||||
| 		int64_t transfer_total {0u}; | ||||
| 		float transfer_rate {0.f}; | ||||
| 		if (o.all_of<ObjComp::F::TagLocalHaveAll>() && fts->total_down <= 0) { | ||||
| 		if (upload) { | ||||
| 			// if have all AND no dl -> show upload progress | ||||
| 			ImGui::TextUnformatted("  up"); | ||||
| 			transfer_total = fts->total_up; | ||||
| @@ -1179,7 +1199,12 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { | ||||
| 		} | ||||
| 		ImGui::SameLine(); | ||||
|  | ||||
| 		float fraction = float(transfer_total) / total_size; | ||||
| 		float fraction{0.f}; | ||||
| 		if (total_size > 0) { | ||||
| 			fraction = float(transfer_total) / total_size; | ||||
| 		} else if (o.all_of<ObjComp::F::TagLocalHaveAll>()) { | ||||
| 			fraction = 1.f; | ||||
| 		} | ||||
|  | ||||
| 		char overlay_buf[128]; | ||||
| 		if (transfer_rate > 0.000001f) { | ||||
| @@ -1207,11 +1232,57 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { | ||||
| 			std::snprintf(overlay_buf, sizeof(overlay_buf), "%.1f%%", fraction * 100 + 0.01f); | ||||
| 		} | ||||
|  | ||||
| 		ImGui::ProgressBar( | ||||
| 			fraction, | ||||
| 			{-FLT_MIN, TEXT_BASE_HEIGHT}, | ||||
| 			overlay_buf | ||||
| 		); | ||||
| 		if ( | ||||
| 			(!upload && !o.all_of<ObjComp::F::TagLocalHaveAll>() && o.all_of<ObjComp::F::LocalHaveBitset>()) || | ||||
| 			(upload && o.all_of<ObjComp::F::RemoteHaveBitset>()) | ||||
| 		) { | ||||
| 			ImGui::BeginGroup(); | ||||
|  | ||||
| 			// TODO: hights are all off | ||||
|  | ||||
| 			ImGui::ProgressBar( | ||||
| 				fraction, | ||||
| 				{-FLT_MIN, TEXT_BASE_HEIGHT*0.66f}, | ||||
| 				overlay_buf | ||||
| 			); | ||||
|  | ||||
| 			ImVec2 orig_curser_pos = ImGui::GetCursorPos(); | ||||
| 			const ImVec2 bar_size{ImGui::GetContentRegionAvail().x, TEXT_BASE_HEIGHT*0.15f}; | ||||
| 			// deploy dummy and check visibility | ||||
| 			ImGui::Dummy(bar_size); | ||||
| 			if (ImGui::IsItemVisible()) { | ||||
| 				ImGui::SetCursorPos(orig_curser_pos); // reset before dummy | ||||
|  | ||||
| 				auto const cursor_start_vec = ImGui::GetCursorScreenPos(); | ||||
| 				// TODO: replace with own version, so we dont have to internal | ||||
| 				ImGui::RenderFrame( | ||||
| 					cursor_start_vec, | ||||
| 					{ | ||||
| 						cursor_start_vec.x + bar_size.x, | ||||
| 						cursor_start_vec.y + bar_size.y | ||||
| 					}, | ||||
| 					ImGui::GetColorU32(ImGuiCol_FrameBg), | ||||
| 					false | ||||
| 				); | ||||
|  | ||||
| 				auto [id, img_width, img_height] = _b_tc.get(o); | ||||
| 				ImGui::Image( | ||||
| 					id, | ||||
| 					bar_size, | ||||
| 					{0.f, 0.f}, // default | ||||
| 					{1.f, 1.f}, // default | ||||
| 					ImGui::GetStyleColorVec4(ImGuiCol_PlotHistogram) | ||||
| 				); | ||||
| 			} | ||||
|  | ||||
| 			ImGui::EndGroup(); | ||||
| 		} else { | ||||
| 			ImGui::ProgressBar( | ||||
| 				fraction, | ||||
| 				{-FLT_MIN, TEXT_BASE_HEIGHT}, | ||||
| 				overlay_buf | ||||
| 			); | ||||
| 		} | ||||
| 	} else { | ||||
| 		// infinite scrolling progressbar fallback | ||||
| 		ImGui::TextUnformatted("  ??"); | ||||
| @@ -1223,16 +1294,6 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) { | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	if (!o.all_of<ObjComp::F::TagLocalHaveAll>() && o.all_of<ObjComp::F::LocalHaveBitset>()) { | ||||
| 		// texture based on have bitset | ||||
| 		// TODO: missing have chunks/chunksize to get the correct size | ||||
|  | ||||
| 		//const auto& bitest = o.get<ObjComp::F::LocalHaveBitset>().have; | ||||
| 		// generate 1bit sdlsurface zerocopy using bitset.data() and bitset.size_bytes() | ||||
| 		// optionally scale down filtered (would copy) | ||||
| 		// update texture? in cache? | ||||
| 	} | ||||
|  | ||||
| 	if (o.all_of<ObjComp::F::FrameDims>()) { | ||||
| 		const auto& frame_dims = o.get<ObjComp::F::FrameDims>(); | ||||
|  | ||||
| @@ -1717,3 +1778,11 @@ void ChatGui4::sendFileList(const std::vector<std::string_view>& list) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool ChatGui4::onEvent(const ObjectStore::Events::ObjectUpdate& e) { | ||||
| 	if (e.e.any_of<ObjComp::F::LocalHaveBitset, ObjComp::F::RemoteHaveBitset>()) { | ||||
| 		_b_tc.stale(e.e); | ||||
| 	} | ||||
|  | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <solanaceae/object_store/fwd.hpp> | ||||
| #include <solanaceae/object_store/object_store.hpp> | ||||
| #include <solanaceae/message3/registry_message_model.hpp> | ||||
| #include <solanaceae/util/config_model.hpp> | ||||
|  | ||||
| @@ -10,6 +10,7 @@ | ||||
| #include "./texture_cache.hpp" | ||||
| #include "./tox_avatar_loader.hpp" | ||||
| #include "./message_image_loader.hpp" | ||||
| #include "./bitset_image_loader.hpp" | ||||
| #include "./chat_gui/file_selector.hpp" | ||||
| #include "./chat_gui/send_image_popup.hpp" | ||||
|  | ||||
| @@ -23,15 +24,19 @@ | ||||
|  | ||||
| using ContactTextureCache = TextureCache<void*, Contact3, ToxAvatarLoader>; | ||||
| using MessageTextureCache = TextureCache<void*, Message3Handle, MessageImageLoader>; | ||||
| using BitsetTextureCache = TextureCache<void*, ObjectHandle, BitsetImageLoader>; | ||||
|  | ||||
| class ChatGui4 { | ||||
| class ChatGui4 : public ObjectStoreEventI { | ||||
| 	ConfigModelI& _conf; | ||||
| 	ObjectStore2& _os; | ||||
| 	ObjectStoreEventProviderI::SubscriptionReference _os_sr; | ||||
| 	RegistryMessageModelI& _rmm; | ||||
| 	Contact3Registry& _cr; | ||||
|  | ||||
| 	ContactTextureCache& _contact_tc; | ||||
| 	MessageTextureCache& _msg_tc; | ||||
| 	BitsetImageLoader _bil; | ||||
| 	BitsetTextureCache _b_tc; | ||||
|  | ||||
| 	Theme& _theme; | ||||
|  | ||||
| @@ -86,6 +91,9 @@ class ChatGui4 { | ||||
| 		//bool renderSubContactListContact(const Contact3 c, const bool selected) const; | ||||
|  | ||||
| 		void pasteFile(const char* mime_type); | ||||
|  | ||||
| 	protected: | ||||
| 		bool onEvent(const ObjectStore::Events::ObjectUpdate&) override; | ||||
| }; | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -55,6 +55,7 @@ ImageLoaderSDLImage::ImageInfo ImageLoaderSDLImage::loadInfoFromMemory(const uin | ||||
| 	// we ignore tga | ||||
| 	auto ext_opt = getExt(ios); | ||||
| 	if (!ext_opt.has_value()) { | ||||
| 		SDL_CloseIO(ios); | ||||
| 		return res; | ||||
| 	} | ||||
|  | ||||
| @@ -80,6 +81,7 @@ ImageLoaderSDLImage::ImageResult ImageLoaderSDLImage::loadFromMemoryRGBA(const u | ||||
| 	// we ignore tga | ||||
| 	auto ext_opt = getExt(ios); | ||||
| 	if (!ext_opt.has_value()) { | ||||
| 		SDL_CloseIO(ios); | ||||
| 		return res; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -30,29 +30,29 @@ MessageImageLoader::MessageImageLoader(void) { | ||||
| 	_image_loaders.push_back(std::make_unique<ImageLoaderSDLImage>()); | ||||
| } | ||||
|  | ||||
| std::optional<TextureEntry> MessageImageLoader::load(TextureUploaderI& tu, Message3Handle m) { | ||||
| TextureLoaderResult MessageImageLoader::load(TextureUploaderI& tu, Message3Handle m) { | ||||
| 	if (!static_cast<bool>(m)) { | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	if (m.all_of<Message::Components::TagNotImage>()) { | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	if (!m.all_of<Message::Components::MessageFileObject>()) { | ||||
| 		// not a file message | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
| 	const auto& o = m.get<Message::Components::MessageFileObject>().o; | ||||
|  | ||||
| 	if (!static_cast<bool>(o)) { | ||||
| 		std::cerr << "MIL error: invalid object in file message\n"; | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	if (!o.all_of<ObjComp::Ephemeral::Backend, ObjComp::F::SingleInfo>()) { | ||||
| 		std::cerr << "MIL error: object missing backend (?)\n"; | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	// TODO: handle collections | ||||
| @@ -60,40 +60,40 @@ std::optional<TextureEntry> MessageImageLoader::load(TextureUploaderI& tu, Messa | ||||
|  | ||||
| 	if (file_size > 50*1024*1024) { | ||||
| 		std::cerr << "MIL error: image file too large\n"; | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	if (file_size == 0) { | ||||
| 		std::cerr << "MIL warning: empty file\n"; | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	if (!o.all_of<ObjComp::F::TagLocalHaveAll>()) { | ||||
| 		// not ready yet | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	auto* file_backend = o.get<ObjComp::Ephemeral::Backend>().ptr; | ||||
| 	if (file_backend == nullptr) { | ||||
| 		std::cerr << "MIL error: object backend nullptr\n"; | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	auto file2 = file_backend->file2(o, StorageBackendI::FILE2_READ); | ||||
| 	if (!file2 || !file2->isGood() || !file2->can_read) { | ||||
| 		std::cerr << "MIL error: creating file2 from object via backendI\n"; | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	auto read_data = file2->read(file_size, 0); | ||||
| 	if (read_data.ptr == nullptr) { | ||||
| 		std::cerr << "MMIL error: reading from file2 returned nullptr\n"; | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	if (read_data.size != file_size) { | ||||
| 		std::cerr << "MIL error: reading from file2 size missmatch, should be " << file_size << ", is " << read_data.size << "\n"; | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	// try all loaders after another | ||||
| @@ -107,7 +107,7 @@ std::optional<TextureEntry> MessageImageLoader::load(TextureUploaderI& tu, Messa | ||||
| 		new_entry.timestamp_last_rendered = Message::getTimeMS(); | ||||
| 		new_entry.current_texture = 0; | ||||
| 		for (const auto& [ms, data] : res.frames) { | ||||
| 			const auto n_t = tu.uploadRGBA(data.data(), res.width, res.height); | ||||
| 			const auto n_t = tu.upload(data.data(), res.width, res.height); | ||||
| 			new_entry.textures.push_back(n_t); | ||||
| 			new_entry.frame_duration.push_back(ms); | ||||
| 		} | ||||
| @@ -117,10 +117,10 @@ std::optional<TextureEntry> MessageImageLoader::load(TextureUploaderI& tu, Messa | ||||
|  | ||||
| 		std::cout << "MIL: loaded image file o:" << /*file_path*/ entt::to_integral(o.entity()) << "\n"; | ||||
|  | ||||
| 		return new_entry; | ||||
| 		return {new_entry}; | ||||
| 	} | ||||
|  | ||||
| 	std::cerr << "MIL error: failed to load message (unhandled format)\n"; | ||||
| 	return std::nullopt; | ||||
| 	return {std::nullopt}; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,6 @@ class MessageImageLoader { | ||||
|  | ||||
| 	public: | ||||
| 		MessageImageLoader(void); | ||||
| 		std::optional<TextureEntry> load(TextureUploaderI& tu, Message3Handle c); | ||||
| 		TextureLoaderResult load(TextureUploaderI& tu, Message3Handle m); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -8,21 +8,6 @@ | ||||
|  | ||||
| namespace ObjectStore::Components { | ||||
|  | ||||
| 	// until i find a better name | ||||
| 	namespace File { | ||||
|  | ||||
| 		// ephemeral?, not sure saving this to disk makes sense | ||||
| 		// tag remove have all? | ||||
| 		struct RemoteHaveBitset { | ||||
| 			struct Entry { | ||||
| 				bool have_all {false}; | ||||
| 				BitSet have; | ||||
| 			}; | ||||
| 			entt::dense_map<Contact3, Entry> others; | ||||
| 		}; | ||||
|  | ||||
| 	} // File | ||||
|  | ||||
| 	namespace Ephemeral { | ||||
|  | ||||
| 		namespace File { | ||||
|   | ||||
| @@ -18,8 +18,6 @@ constexpr std::string_view entt::type_name<x>::value() noexcept { \ | ||||
|  | ||||
| // cross compile(r) stable ids | ||||
|  | ||||
| DEFINE_COMP_ID(ObjComp::F::RemoteHaveBitset) | ||||
|  | ||||
| DEFINE_COMP_ID(ObjComp::Ephemeral::File::TransferStatsSeparated) | ||||
|  | ||||
| #undef DEFINE_COMP_ID | ||||
|   | ||||
| @@ -46,7 +46,7 @@ TextureEntry generateTestAnim(TextureUploaderI& tu) { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		const auto n_t = tu.uploadRGBA(pixels.data(), width, height); | ||||
| 		const auto n_t = tu.upload(pixels.data(), width, height); | ||||
|  | ||||
| 		// this is so ugly | ||||
| 		new_entry.textures.emplace_back(n_t); | ||||
|   | ||||
| @@ -5,6 +5,10 @@ | ||||
| #include <entt/container/dense_map.hpp> | ||||
| #include <entt/container/dense_set.hpp> | ||||
|  | ||||
| #include <optional> | ||||
| #include <vector> | ||||
| #include <cassert> | ||||
|  | ||||
| #include <iostream> | ||||
|  | ||||
| struct TextureEntry { | ||||
| @@ -70,6 +74,11 @@ struct TextureEntry { | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct TextureLoaderResult { | ||||
| 	std::optional<TextureEntry> texture; | ||||
| 	bool keep_trying{false}; // if set, cant be cleared, as some async might be going on | ||||
| }; | ||||
|  | ||||
| TextureEntry generateTestAnim(TextureUploaderI& tu); | ||||
|  | ||||
| // fwd | ||||
| @@ -91,6 +100,7 @@ struct TextureCache { | ||||
|  | ||||
| 	entt::dense_map<KeyType, TextureEntry> _cache; | ||||
| 	entt::dense_set<KeyType> _to_load; | ||||
| 	// to_reload // to_update? _marked_stale? | ||||
|  | ||||
| 	const uint64_t ms_before_purge {60 * 1000ull}; | ||||
| 	const size_t min_count_before_purge {0}; // starts purging after that | ||||
| @@ -134,6 +144,18 @@ struct TextureCache { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// markes a texture as stale and will reload it | ||||
| 	// only if it already is loaded, does not update ts | ||||
| 	bool stale(const KeyType& key) { | ||||
| 		auto it = _cache.find(key); | ||||
|  | ||||
| 		if (it == _cache.end()) { | ||||
| 			return false; | ||||
| 		} | ||||
| 		_to_load.insert(key); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	float update(void) { | ||||
| 		const uint64_t ts_now = Message::getTimeMS(); | ||||
| 		uint64_t ts_min_next = ts_now + ms_before_purge; | ||||
| @@ -144,7 +166,10 @@ struct TextureCache { | ||||
| 				const uint64_t ts_next = te.doAnimation(ts_now); | ||||
| 				te.rendered_this_frame = false; | ||||
| 				ts_min_next = std::min(ts_min_next, ts_next); | ||||
| 			} else if (_cache.size() > min_count_before_purge && ts_now - te.timestamp_last_rendered >= ms_before_purge) { | ||||
| 			} else if ( | ||||
| 				_cache.size() > min_count_before_purge && | ||||
| 				ts_now - te.timestamp_last_rendered >= ms_before_purge | ||||
| 			) { | ||||
| 				to_purge.push_back(key); | ||||
| 			} | ||||
| 		} | ||||
| @@ -159,31 +184,64 @@ struct TextureCache { | ||||
|  | ||||
| 	void invalidate(const std::vector<KeyType>& to_purge) { | ||||
| 		for (const auto& key : to_purge) { | ||||
| 			if (_to_load.count(key)) { | ||||
| 				// TODO: only remove if not keep trying | ||||
| 				_to_load.erase(key); | ||||
| 			} | ||||
| 			if (_cache.count(key)) { | ||||
| 				for (const auto& tex_id : _cache.at(key).textures) { | ||||
| 					_tu.destroy(tex_id); | ||||
| 				} | ||||
| 				_cache.erase(key); | ||||
| 			} | ||||
| 			_cache.erase(key); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// returns true if there is still work queued up | ||||
| 	bool workLoadQueue(void) { | ||||
| 		auto it = _to_load.begin(); | ||||
| 		for (; it != _to_load.end(); it++) { | ||||
| 		auto it = _to_load.cbegin(); | ||||
| 		for (; it != _to_load.cend(); it++) { | ||||
| 			auto new_entry_opt = _l.load(_tu, *it); | ||||
| 			if (new_entry_opt.has_value()) { | ||||
| 				_cache.emplace(*it, new_entry_opt.value()); | ||||
| 				it = _to_load.erase(it); | ||||
| 			if (_cache.count(*it)) { | ||||
| 				if (new_entry_opt.texture.has_value()) { | ||||
| 					auto old_entry = _cache.at(*it); // copy | ||||
| 					assert(!old_entry.textures.empty()); | ||||
| 					for (const auto& tex_id : old_entry.textures) { | ||||
| 						_tu.destroy(tex_id); | ||||
| 					} | ||||
|  | ||||
| 				// TODO: not a good idea? | ||||
| 				break; // end load from queue/onlyload 1 per update | ||||
| 					_cache.erase(*it); | ||||
| 					auto& new_entry = _cache[*it] = new_entry_opt.texture.value(); | ||||
| 					// TODO: make update interface and let loader handle this | ||||
| 					//new_entry.current_texture = old_entry.current_texture; // ?? | ||||
| 					new_entry.rendered_this_frame = old_entry.rendered_this_frame; | ||||
| 					new_entry.timestamp_last_rendered = old_entry.timestamp_last_rendered; | ||||
|  | ||||
| 					it = _to_load.erase(it); | ||||
|  | ||||
| 					// TODO: not a good idea? | ||||
| 					break; // end load from queue/onlyload 1 per update | ||||
| 				} else if (!new_entry_opt.keep_trying) { | ||||
| 					// failed to load and the loader is done | ||||
| 					it = _to_load.erase(it); | ||||
| 				} | ||||
| 			} else { | ||||
| 				if (new_entry_opt.texture.has_value()) { | ||||
| 					_cache.emplace(*it, new_entry_opt.texture.value()); | ||||
| 					_cache.at(*it).rendered_this_frame = true; // ? | ||||
| 					it = _to_load.erase(it); | ||||
|  | ||||
| 					// TODO: not a good idea? | ||||
| 					break; // end load from queue/onlyload 1 per update | ||||
| 				} else if (!new_entry_opt.keep_trying) { | ||||
| 					// failed to load and the loader is done | ||||
| 					it = _to_load.erase(it); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// peak | ||||
| 		return it != _to_load.end(); | ||||
| 		// peek | ||||
| 		return it != _to_load.cend(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| #include "./image_loader_qoi.hpp" | ||||
| #include "./image_loader_webp.hpp" | ||||
| #include "./image_loader_sdl_image.hpp" | ||||
| #include "texture_uploader.hpp" | ||||
|  | ||||
| #include <solanaceae/contact/components.hpp> | ||||
| #include <solanaceae/tox_contacts/components.hpp> | ||||
| @@ -119,9 +120,9 @@ static std::vector<uint8_t> generateToxIdenticon(const ToxKey& key) { | ||||
| 	return pixels; | ||||
| } | ||||
|  | ||||
| std::optional<TextureEntry> ToxAvatarLoader::load(TextureUploaderI& tu, Contact3 c) { | ||||
| TextureLoaderResult ToxAvatarLoader::load(TextureUploaderI& tu, Contact3 c) { | ||||
| 	if (!_cr.valid(c)) { | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	if (_cr.all_of<Contact::Components::AvatarMemory>(c)) { | ||||
| @@ -134,13 +135,13 @@ std::optional<TextureEntry> ToxAvatarLoader::load(TextureUploaderI& tu, Contact3 | ||||
| 		new_entry.width = a_m.width; | ||||
| 		new_entry.height = a_m.height; | ||||
|  | ||||
| 		const auto n_t = tu.uploadRGBA(a_m.data.data(), a_m.width, a_m.height); | ||||
| 		const auto n_t = tu.upload(a_m.data.data(), a_m.width, a_m.height); | ||||
| 		new_entry.textures.push_back(n_t); | ||||
| 		new_entry.frame_duration.push_back(250); | ||||
|  | ||||
| 		std::cout << "TAL: loaded memory buffer\n"; | ||||
|  | ||||
| 		return new_entry; | ||||
| 		return {new_entry}; | ||||
| 	} | ||||
|  | ||||
| 	if (_cr.all_of<Contact::Components::AvatarFile>(c)) { | ||||
| @@ -169,7 +170,7 @@ std::optional<TextureEntry> ToxAvatarLoader::load(TextureUploaderI& tu, Contact3 | ||||
| 				new_entry.timestamp_last_rendered = Message::getTimeMS(); | ||||
| 				new_entry.current_texture = 0; | ||||
| 				for (const auto& [ms, data] : res.frames) { | ||||
| 					const auto n_t = tu.uploadRGBA(data.data(), res.width, res.height); | ||||
| 					const auto n_t = tu.upload(data.data(), res.width, res.height); | ||||
| 					new_entry.textures.push_back(n_t); | ||||
| 					new_entry.frame_duration.push_back(ms); | ||||
| 				} | ||||
| @@ -179,7 +180,7 @@ std::optional<TextureEntry> ToxAvatarLoader::load(TextureUploaderI& tu, Contact3 | ||||
|  | ||||
| 				std::cout << "TAL: loaded image file " << a_f.file_path << "\n"; | ||||
|  | ||||
| 				return new_entry; | ||||
| 				return {new_entry}; | ||||
| 			} | ||||
| 		} | ||||
| 	} // continues if loading img fails | ||||
| @@ -190,7 +191,7 @@ std::optional<TextureEntry> ToxAvatarLoader::load(TextureUploaderI& tu, Contact3 | ||||
| 		Contact::Components::ToxGroupPeerPersistent, | ||||
| 		Contact::Components::ID | ||||
| 	>(c)) { | ||||
| 		return std::nullopt; | ||||
| 		return {std::nullopt}; | ||||
| 	} | ||||
|  | ||||
| 	std::vector<uint8_t> pixels; | ||||
| @@ -212,7 +213,7 @@ std::optional<TextureEntry> ToxAvatarLoader::load(TextureUploaderI& tu, Contact3 | ||||
| 	new_entry.timestamp_last_rendered = Message::getTimeMS(); | ||||
| 	new_entry.current_texture = 0; | ||||
|  | ||||
| 	const auto n_t = tu.uploadRGBA(pixels.data(), 5, 5, TextureUploaderI::NEAREST); | ||||
| 	const auto n_t = tu.upload(pixels.data(), 5, 5, TextureUploaderI::RGBA, TextureUploaderI::NEAREST); | ||||
| 	new_entry.textures.push_back(n_t); | ||||
| 	new_entry.frame_duration.push_back(250); | ||||
|  | ||||
| @@ -221,6 +222,6 @@ std::optional<TextureEntry> ToxAvatarLoader::load(TextureUploaderI& tu, Contact3 | ||||
|  | ||||
| 	std::cout << "TAL: generated ToxIdenticon\n"; | ||||
|  | ||||
| 	return new_entry; | ||||
| 	return {new_entry}; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,6 @@ class ToxAvatarLoader { | ||||
|  | ||||
| 	public: | ||||
| 		ToxAvatarLoader(Contact3Registry& cr); | ||||
| 		std::optional<TextureEntry> load(TextureUploaderI& tu, Contact3 c); | ||||
| 		TextureLoaderResult load(TextureUploaderI& tu, Contact3 c); | ||||
| }; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user