Compare commits

...

9 Commits

Author SHA1 Message Date
cc3f430bab rework tc and move tcs out of cg into main screen, rework render pp
now respecting animation timing
2024-02-05 16:06:12 +01:00
139db5b03b faster texture cache loading in low fps modes 2024-02-05 12:50:36 +01:00
7d0e5c80bd lil dep update 2024-02-04 12:48:04 +01:00
f716ad9dd1 limit max main loop sleep 2024-02-03 20:49:52 +01:00
671772a20e min fps for inactive reduced now 1fps 2024-02-03 19:07:14 +01:00
b0173f6d68 tox iterate interval pow(1.6)
fix faux offline inbetween timer
crop by default
2024-02-03 15:00:32 +01:00
3da5872df8 fix tffom and have it actually functioning 2024-02-03 01:05:50 +01:00
3deb6e8469 fix using bool for timestamps (oops) 2024-02-02 20:55:20 +01:00
0c674e0137 add tox friend faux offline message (still wonky) + small file copy error handling 2024-02-02 20:26:50 +01:00
15 changed files with 513 additions and 63 deletions

View File

@ -61,6 +61,9 @@ add_executable(tomato
./tox_dht_cap_histo.hpp ./tox_dht_cap_histo.hpp
./tox_dht_cap_histo.cpp ./tox_dht_cap_histo.cpp
./tox_friend_faux_offline_messaging.hpp
./tox_friend_faux_offline_messaging.cpp
./chat_gui4.hpp ./chat_gui4.hpp
./chat_gui4.cpp ./chat_gui4.cpp
) )

View File

@ -37,7 +37,7 @@ namespace Components {
} // Components } // Components
static float lerp(float a, float b, float t) { static constexpr float lerp(float a, float b, float t) {
return a + t * (b - a); return a + t * (b - a);
} }
@ -96,13 +96,21 @@ const void* clipboard_callback(void* userdata, const char* mime_type, size_t* si
} }
void ChatGui4::setClipboardData(std::vector<std::string> mime_types, std::shared_ptr<std::vector<uint8_t>>&& data) { void ChatGui4::setClipboardData(std::vector<std::string> mime_types, std::shared_ptr<std::vector<uint8_t>>&& data) {
std::vector<const char*> tmp_mimetype_list; if (!static_cast<bool>(data)) {
for (const auto& mime_type : mime_types) { std::cerr << "CG error: tried to set clipboard with empty shp\n";
tmp_mimetype_list.push_back(mime_type.data()); return;
} }
if (data->empty()) {
std::cerr << "CG error: tried to set clipboard with empty data\n";
return;
}
std::vector<const char*> tmp_mimetype_list;
std::lock_guard lg{_set_clipboard_data_mutex}; std::lock_guard lg{_set_clipboard_data_mutex};
for (const auto& mime_type : mime_types) { for (const auto& mime_type : mime_types) {
tmp_mimetype_list.push_back(mime_type.data());
_set_clipboard_data[mime_type] = data; _set_clipboard_data[mime_type] = data;
} }
@ -113,8 +121,10 @@ ChatGui4::ChatGui4(
ConfigModelI& conf, ConfigModelI& conf,
RegistryMessageModel& rmm, RegistryMessageModel& rmm,
Contact3Registry& cr, Contact3Registry& cr,
TextureUploaderI& tu TextureUploaderI& tu,
) : _conf(conf), _rmm(rmm), _cr(cr), _tal(_cr), _contact_tc(_tal, tu), _msg_tc(_mil, tu), _sip(tu) { ContactTextureCache& contact_tc,
MessageTextureCache& msg_tc
) : _conf(conf), _rmm(rmm), _cr(cr), _contact_tc(contact_tc), _msg_tc(msg_tc), _sip(tu) {
} }
ChatGui4::~ChatGui4(void) { ChatGui4::~ChatGui4(void) {
@ -128,20 +138,7 @@ ChatGui4::~ChatGui4(void) {
//} //}
} }
void ChatGui4::render(float time_delta) { float ChatGui4::render(float time_delta) {
if (!_cr.storage<Contact::Components::TagAvatarInvalidate>().empty()) { // handle force-reloads for avatars
std::vector<Contact3> to_purge;
_cr.view<Contact::Components::TagAvatarInvalidate>().each([&to_purge](const Contact3 c) {
to_purge.push_back(c);
});
_cr.remove<Contact::Components::TagAvatarInvalidate>(to_purge.cbegin(), to_purge.cend());
_contact_tc.invalidate(to_purge);
}
// ACTUALLY NOT IF RENDERED, MOVED LOGIC TO ABOVE
// it might unload textures, so it needs to be done before rendering
_contact_tc.update();
_msg_tc.update();
_fss.render(); _fss.render();
_sip.render(time_delta); _sip.render(time_delta);
@ -619,8 +616,7 @@ void ChatGui4::render(float time_delta) {
} }
ImGui::End(); ImGui::End();
_contact_tc.workLoadQueue(); return 1000.f; // TODO: higher min fps?
_msg_tc.workLoadQueue();
} }
void ChatGui4::sendFilePath(const char* file_path) { void ChatGui4::sendFilePath(const char* file_path) {

View File

@ -9,7 +9,8 @@
#include "./message_image_loader.hpp" #include "./message_image_loader.hpp"
#include "./file_selector.hpp" #include "./file_selector.hpp"
#include "./send_image_popup.hpp" #include "./send_image_popup.hpp"
#include "entt/container/dense_map.hpp"
#include <entt/container/dense_map.hpp>
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
@ -17,15 +18,16 @@
#include <mutex> #include <mutex>
#include <memory> #include <memory>
using ContactTextureCache = TextureCache<void*, Contact3, ToxAvatarLoader>;
using MessageTextureCache = TextureCache<void*, Message3Handle, MessageImageLoader>;
class ChatGui4 { class ChatGui4 {
ConfigModelI& _conf; ConfigModelI& _conf;
RegistryMessageModel& _rmm; RegistryMessageModel& _rmm;
Contact3Registry& _cr; Contact3Registry& _cr;
ToxAvatarLoader _tal; ContactTextureCache& _contact_tc;
TextureCache<void*, Contact3, ToxAvatarLoader> _contact_tc; MessageTextureCache& _msg_tc;
MessageImageLoader _mil;
TextureCache<void*, Message3Handle, MessageImageLoader> _msg_tc;
FileSelector _fss; FileSelector _fss;
SendImagePopup _sip; SendImagePopup _sip;
@ -52,12 +54,14 @@ class ChatGui4 {
ConfigModelI& conf, ConfigModelI& conf,
RegistryMessageModel& rmm, RegistryMessageModel& rmm,
Contact3Registry& cr, Contact3Registry& cr,
TextureUploaderI& tu TextureUploaderI& tu,
ContactTextureCache& contact_tc,
MessageTextureCache& msg_tc
); );
~ChatGui4(void); ~ChatGui4(void);
public: public:
void render(float time_delta); float render(float time_delta);
public: public:
bool any_unread {false}; bool any_unread {false};

View File

@ -178,9 +178,13 @@ int main(int argc, char** argv) {
//) //)
//)); //));
const float min_delay = std::min<float>( const float min_delay =
screen->nextTick() - time_delta_tick, std::min<float>(
screen->nextRender() - time_delta_render std::min<float>(
screen->nextTick() - time_delta_tick,
screen->nextRender() - time_delta_render
),
0.25f // dont sleep too long
) * 1000.f; ) * 1000.f;
if (min_delay > 0.f) { if (min_delay > 0.f) {

View File

@ -1,10 +1,13 @@
#include "./main_screen.hpp" #include "./main_screen.hpp"
#include <solanaceae/contact/components.hpp>
#include <imgui/imgui.h> #include <imgui/imgui.h>
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <memory> #include <memory>
#include <cmath>
MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path, std::string save_password, std::vector<std::string> plugins) : MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path, std::string save_password, std::vector<std::string> plugins) :
renderer(renderer_), renderer(renderer_),
@ -16,10 +19,15 @@ MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path, std::stri
tcm(cr, tc, tc), tcm(cr, tc, tc),
tmm(rmm, cr, tcm, tc, tc), tmm(rmm, cr, tcm, tc, tc),
ttm(rmm, cr, tcm, tc, tc), ttm(rmm, cr, tcm, tc, tc),
tffom(cr, rmm, tcm, tc, tc),
mmil(rmm), mmil(rmm),
tam(rmm, cr, conf), tam(rmm, cr, conf),
sdlrtu(renderer_), sdlrtu(renderer_),
cg(conf, rmm, cr, sdlrtu), tal(cr),
contact_tc(tal, sdlrtu),
mil(),
msg_tc(mil, sdlrtu),
cg(conf, rmm, cr, sdlrtu, contact_tc, msg_tc),
sw(conf), sw(conf),
tuiu(tc, conf), tuiu(tc, conf),
tdch(tpi) tdch(tpi)
@ -166,7 +174,22 @@ Screen* MainScreen::render(float time_delta, bool&) {
const float pm_interval = pm.render(time_delta); // render const float pm_interval = pm.render(time_delta); // render
cg.render(time_delta); // render // TODO: move this somewhere else!!!
// needs both tal and tc <.<
if (!cr.storage<Contact::Components::TagAvatarInvalidate>().empty()) { // handle force-reloads for avatars
std::vector<Contact3> to_purge;
cr.view<Contact::Components::TagAvatarInvalidate>().each([&to_purge](const Contact3 c) {
to_purge.push_back(c);
});
cr.remove<Contact::Components::TagAvatarInvalidate>(to_purge.cbegin(), to_purge.cend());
contact_tc.invalidate(to_purge);
}
// ACTUALLY NOT IF RENDERED, MOVED LOGIC TO ABOVE
// it might unload textures, so it needs to be done before rendering
const float ctc_interval = contact_tc.update();
const float msgtc_interval = msg_tc.update();
const float cg_interval = cg.render(time_delta); // render
sw.render(); // render sw.render(); // render
tuiu.render(); // render tuiu.render(); // render
tdch.render(); // render tdch.render(); // render
@ -215,20 +238,136 @@ Screen* MainScreen::render(float time_delta, bool&) {
ImGui::ShowDemoWindow(); ImGui::ShowDemoWindow();
} }
if ( float tc_unfinished_queue_interval;
_fps_perf_mode > 1 // TODO: magic { // load rendered but not loaded textures
) { bool unfinished_work_queue = contact_tc.workLoadQueue();
// powersave forces 250ms unfinished_work_queue = unfinished_work_queue || msg_tc.workLoadQueue();
_render_interval = 1.f/4.f;
} else if ( if (unfinished_work_queue) {
_time_since_event > 1.f && ( // 1sec cool down tc_unfinished_queue_interval = 0.1f; // so we can get images loaded faster
_fps_perf_mode == 1 || // TODO: magic } else {
_window_hidden tc_unfinished_queue_interval = 1.f; // TODO: higher min fps?
}
}
// calculate interval for next frame
// normal:
// - if < 1.5sec since last event
// - min all and clamp(1/60, 1/1)
// - if < 30sec since last event
// - min all (anim + everything else) clamp(1/60, 1/1) (maybe less?)
// - else
// - min without anim and clamp(1/60, 1/1) (maybe more?)
// reduced:
// - if < 1sec since last event
// - min all and clamp(1/60, 1/1)
// - if < 10sec since last event
// - min all (anim + everything else) clamp(1/10, 1/1)
// - else
// - min without anim and max clamp(1/10, 1/1)
// powersave:
// - if < 0sec since last event
// - (ignored)
// - if < 1sec since last event
// - min all (anim + everything else) clamp(1/8, 1/1)
// - else
// - min without anim and clamp(1/1, 1/1)
struct PerfProfileRender {
float low_delay_window {1.5f};
float low_delay_min {1.f/60.f};
float low_delay_max {1.f/30.f};
float mid_delay_window {30.f};
float mid_delay_min {1.f/60.f};
float mid_delay_max {1.f/2.f};
// also when main window hidden
float else_delay_min {1.f/60.f};
float else_delay_max {1.f/2.f};
};
const static PerfProfileRender normalPerfProfile{
//1.5f, // low_delay_window
//1.f/60.f, // low_delay_min
//1.f/30.f, // low_delay_max
//30.f, // mid_delay_window
//1.f/60.f, // mid_delay_min
//1.f/2.f, // mid_delay_max
//1.f/60.f, // else_delay_min
//1.f/2.f, // else_delay_max
};
const static PerfProfileRender reducedPerfProfile{
1.f, // low_delay_window
1.f/60.f, // low_delay_min
1.f/30.f, // low_delay_max
10.f, // mid_delay_window
1.f/10.f, // mid_delay_min
1.f/4.f, // mid_delay_max
1.f/10.f, // else_delay_min
1.f, // else_delay_max
};
// TODO: fix powersave by adjusting it in the events handler (make ppr member)
const static PerfProfileRender powersavePerfProfile{
// no window -> ignore first case
0.f, // low_delay_window
1.f, // low_delay_min
1.f, // low_delay_max
1.f, // mid_delay_window
1.f/8.f, // mid_delay_min
1.f/4.f, // mid_delay_max
1.f, // else_delay_min
1.f, // else_delay_max
};
const PerfProfileRender& curr_profile =
// TODO: magic
_fps_perf_mode > 1
? powersavePerfProfile
: (
_fps_perf_mode == 1
? reducedPerfProfile
: normalPerfProfile
) )
) { ;
_render_interval = std::min<float>(1.f/4.f, pm_interval);
// min over non animations in all cases
_render_interval = std::min<float>(pm_interval, cg_interval);
_render_interval = std::min<float>(_render_interval, tc_unfinished_queue_interval);
// low delay time window
if (!_window_hidden && _time_since_event < curr_profile.low_delay_window) {
_render_interval = std::min<float>(_render_interval, ctc_interval);
_render_interval = std::min<float>(_render_interval, msgtc_interval);
_render_interval = std::clamp(
_render_interval,
curr_profile.low_delay_min,
curr_profile.low_delay_max
);
// mid delay time window
} else if (!_window_hidden && _time_since_event < curr_profile.mid_delay_window) {
_render_interval = std::min<float>(_render_interval, ctc_interval);
_render_interval = std::min<float>(_render_interval, msgtc_interval);
_render_interval = std::clamp(
_render_interval,
curr_profile.mid_delay_min,
curr_profile.mid_delay_max
);
// timed out or window hidden
} else { } else {
_render_interval = std::min<float>(1.f/60.f, pm_interval); // no animation timing here
_render_interval = std::clamp(
_render_interval,
curr_profile.else_delay_min,
curr_profile.else_delay_max
);
} }
_time_since_event += time_delta; _time_since_event += time_delta;
@ -241,6 +380,8 @@ Screen* MainScreen::tick(float time_delta, bool& quit) {
tcm.iterate(time_delta); // compute tcm.iterate(time_delta); // compute
const float fo_interval = tffom.tick(time_delta);
tam.iterate(); // compute tam.iterate(); // compute
const float pm_interval = pm.tick(time_delta); // compute const float pm_interval = pm.tick(time_delta); // compute
@ -250,9 +391,17 @@ Screen* MainScreen::tick(float time_delta, bool& quit) {
mts.iterate(); // compute mts.iterate(); // compute
_min_tick_interval = std::min<float>( _min_tick_interval = std::min<float>(
tc.toxIterationInterval()/1000.f, // HACK: pow by 1.6 to increase 50 -> ~500 (~522)
// and it does not change 1
std::pow(tc.toxIterationInterval(), 1.6f)/1000.f,
pm_interval pm_interval
); );
_min_tick_interval = std::min<float>(
_min_tick_interval,
fo_interval
);
//std::cout << "MS: min tick interval: " << _min_tick_interval << "\n";
switch (_compute_perf_mode) { switch (_compute_perf_mode) {
// normal 1ms lower bound // normal 1ms lower bound

View File

@ -21,10 +21,15 @@
#include "./tox_avatar_manager.hpp" #include "./tox_avatar_manager.hpp"
#include "./sdlrenderer_texture_uploader.hpp" #include "./sdlrenderer_texture_uploader.hpp"
#include "./texture_cache.hpp"
#include "./tox_avatar_loader.hpp"
#include "./message_image_loader.hpp"
#include "./chat_gui4.hpp" #include "./chat_gui4.hpp"
#include "./settings_window.hpp" #include "./settings_window.hpp"
#include "./tox_ui_utils.hpp" #include "./tox_ui_utils.hpp"
#include "./tox_dht_cap_histo.hpp" #include "./tox_dht_cap_histo.hpp"
#include "./tox_friend_faux_offline_messaging.hpp"
#include <string> #include <string>
#include <iostream> #include <iostream>
@ -52,6 +57,7 @@ struct MainScreen final : public Screen {
ToxContactModel2 tcm; ToxContactModel2 tcm;
ToxMessageManager tmm; ToxMessageManager tmm;
ToxTransferManager ttm; ToxTransferManager ttm;
ToxFriendFauxOfflineMessaging tffom;
MediaMetaInfoLoader mmil; MediaMetaInfoLoader mmil;
ToxAvatarManager tam; ToxAvatarManager tam;
@ -59,6 +65,11 @@ struct MainScreen final : public Screen {
SDLRendererTextureUploader sdlrtu; SDLRendererTextureUploader sdlrtu;
//OpenGLTextureUploader ogltu; //OpenGLTextureUploader ogltu;
ToxAvatarLoader tal;
TextureCache<void*, Contact3, ToxAvatarLoader> contact_tc;
MessageImageLoader mil;
TextureCache<void*, Message3Handle, MessageImageLoader> msg_tc;
ChatGui4 cg; ChatGui4 cg;
SettingsWindow sw; SettingsWindow sw;
ToxUIUtils tuiu; ToxUIUtils tuiu;
@ -67,7 +78,7 @@ struct MainScreen final : public Screen {
bool _show_tool_style_editor {false}; bool _show_tool_style_editor {false};
bool _window_hidden {false}; bool _window_hidden {false};
bool _window_hidden_ts {0}; uint64_t _window_hidden_ts {0};
float _time_since_event {0.f}; float _time_since_event {0.f};
MainScreen(SDL_Renderer* renderer_, std::string save_path, std::string save_password, std::vector<std::string> plugins); MainScreen(SDL_Renderer* renderer_, std::string save_path, std::string save_password, std::vector<std::string> plugins);

View File

@ -32,7 +32,7 @@ struct SendImagePopup {
Rect crop_rect; Rect crop_rect;
Rect crop_before_drag; Rect crop_before_drag;
bool cropping {false}; bool cropping {true};
bool dragging_last_frame_ul {false}; bool dragging_last_frame_ul {false};
bool dragging_last_frame_lr {false}; bool dragging_last_frame_lr {false};

View File

@ -2,8 +2,9 @@
#include <chrono> #include <chrono>
#include <array> #include <array>
#include <limits>
void TextureEntry::doAnimation(const int64_t ts_now) { int64_t TextureEntry::doAnimation(const int64_t ts_now) {
if (frame_duration.size() > 1) { // is animation if (frame_duration.size() > 1) { // is animation
do { // why is this loop so ugly do { // why is this loop so ugly
const int64_t duration = getDuration(); const int64_t duration = getDuration();
@ -11,11 +12,13 @@ void TextureEntry::doAnimation(const int64_t ts_now) {
timestamp_last_rendered += duration; timestamp_last_rendered += duration;
next(); next();
} else { } else {
break; // return ts for next frame
return timestamp_last_rendered + duration;
} }
} while(true); } while (true);
} else { } else {
timestamp_last_rendered = ts_now; timestamp_last_rendered = ts_now;
return std::numeric_limits<int64_t>::max(); // static image
} }
} }

View File

@ -50,7 +50,8 @@ struct TextureEntry {
current_texture = (current_texture + 1) % frame_duration.size(); current_texture = (current_texture + 1) % frame_duration.size();
} }
void doAnimation(const int64_t ts_now); // returns ts for next frame
int64_t doAnimation(const int64_t ts_now);
template<typename TextureType> template<typename TextureType>
TextureType getID(void) { TextureType getID(void) {
@ -133,14 +134,16 @@ struct TextureCache {
} }
} }
void update(void) { float update(void) {
const uint64_t ts_now = Message::getTimeMS(); const uint64_t ts_now = Message::getTimeMS();
uint64_t ts_min_next = ts_now + ms_before_purge;
std::vector<KeyType> to_purge; std::vector<KeyType> to_purge;
for (auto&& [key, te] : _cache) { for (auto&& [key, te] : _cache) {
if (te.rendered_this_frame) { if (te.rendered_this_frame) {
te.doAnimation(ts_now); const uint64_t ts_next = te.doAnimation(ts_now);
te.rendered_this_frame = false; 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); to_purge.push_back(key);
} }
@ -148,7 +151,10 @@ struct TextureCache {
invalidate(to_purge); invalidate(to_purge);
// we ignore the default texture ts :)
_default_texture.doAnimation(ts_now); _default_texture.doAnimation(ts_now);
return (ts_min_next - ts_now) / 1000.f;
} }
void invalidate(const std::vector<KeyType>& to_purge) { void invalidate(const std::vector<KeyType>& to_purge) {
@ -162,16 +168,22 @@ struct TextureCache {
} }
} }
void workLoadQueue(void) { // returns true if there is still work queued up
for (auto it = _to_load.begin(); it != _to_load.end(); it++) { bool workLoadQueue(void) {
auto it = _to_load.begin();
for (; it != _to_load.end(); it++) {
auto new_entry_opt = _l.load(_tu, *it); auto new_entry_opt = _l.load(_tu, *it);
if (new_entry_opt.has_value()) { if (new_entry_opt.has_value()) {
_cache.emplace(*it, new_entry_opt.value()); _cache.emplace(*it, new_entry_opt.value());
_to_load.erase(it); it = _to_load.erase(it);
// TODO: not a good idea
// TODO: not a good idea?
break; // end load from queue/onlyload 1 per update break; // end load from queue/onlyload 1 per update
} }
} }
// peak
return it != _to_load.end();
} }
}; };

View File

@ -0,0 +1,213 @@
#include "./tox_friend_faux_offline_messaging.hpp"
#include <solanaceae/toxcore/tox_interface.hpp>
#include <solanaceae/contact/components.hpp>
#include <solanaceae/tox_contacts/components.hpp>
#include <solanaceae/message3/components.hpp>
#include <solanaceae/tox_messages/components.hpp>
#include <limits>
#include <cstdint>
//#include <iostream>
namespace Message::Components {
struct LastSendAttempt {
uint64_t ts {0};
};
} // Message::Components
namespace Contact::Components {
struct NextSendAttempt {
uint64_t ts {0};
};
} // Contact::Components
ToxFriendFauxOfflineMessaging::ToxFriendFauxOfflineMessaging(
Contact3Registry& cr,
RegistryMessageModel& rmm,
ToxContactModel2& tcm,
ToxI& t,
ToxEventProviderI& tep
) : _cr(cr), _rmm(rmm), _tcm(tcm), _t(t), _tep(tep) {
_tep.subscribe(this, Tox_Event_Type::TOX_EVENT_FRIEND_CONNECTION_STATUS);
}
float ToxFriendFauxOfflineMessaging::tick(float time_delta) {
_interval_timer -= time_delta;
if (_interval_timer > 0.f) {
return std::max(_interval_timer, 0.001f); // TODO: min next timer
}
// interval ~ once per minute
_interval_timer = 60.f;
const uint64_t ts_now = Message::getTimeMS();
// check ALL
// for each online tox friend
uint64_t min_next_attempt_ts {std::numeric_limits<uint64_t>::max()};
_cr.view<Contact::Components::ToxFriendEphemeral, Contact::Components::ConnectionState>()
.each([this, &min_next_attempt_ts, ts_now](const Contact3 c, const auto& tfe, const auto& cs) {
if (cs.state == Contact::Components::ConnectionState::disconnected) {
// cleanup
if (_cr.all_of<Contact::Components::NextSendAttempt>(c)) {
_cr.remove<Contact::Components::NextSendAttempt>(c);
auto* mr = static_cast<const RegistryMessageModel&>(_rmm).get(c);
if (mr != nullptr) {
mr->storage<Message::Components::LastSendAttempt>().clear();
}
}
} else {
if (!_cr.all_of<Contact::Components::NextSendAttempt>(c)) {
if (false) { // has unsent messages
const auto& nsa = _cr.emplace<Contact::Components::NextSendAttempt>(c, ts_now + uint64_t(_delay_after_cc*1000)); // wait before first message is sent
min_next_attempt_ts = std::min(min_next_attempt_ts, nsa.ts);
}
} else {
auto ret = doFriendMessageCheck(c, tfe);
if (ret == dfmc_Ret::SENT_THIS_TICK) {
const auto ts = _cr.get<Contact::Components::NextSendAttempt>(c).ts = ts_now + uint64_t(_delay_inbetween*1000);
min_next_attempt_ts = std::min(min_next_attempt_ts, ts);
} else if (ret == dfmc_Ret::TOO_SOON) {
// TODO: set to _delay_inbetween? prob expensive for no good reason
min_next_attempt_ts = std::min(min_next_attempt_ts, _cr.get<Contact::Components::NextSendAttempt>(c).ts);
} else {
_cr.remove<Contact::Components::NextSendAttempt>(c);
}
}
}
});
if (min_next_attempt_ts <= ts_now) {
// we (probably) sent this iterate
_interval_timer = 0.1f; // TODO: ugly magic
} else if (min_next_attempt_ts == std::numeric_limits<uint64_t>::max()) {
// nothing to sync or all offline that need syncing
} else {
_interval_timer = std::min(_interval_timer, (min_next_attempt_ts - ts_now) / 1000.f);
}
//std::cout << "TFFOM: iterate (i:" << _interval_timer << ")\n";
return _interval_timer;
}
ToxFriendFauxOfflineMessaging::dfmc_Ret ToxFriendFauxOfflineMessaging::doFriendMessageCheck(const Contact3 c, const Contact::Components::ToxFriendEphemeral& tfe) {
// walk all messages and check if
// unacked message
// timeouts for exising unacked messages expired (send)
auto* mr = static_cast<const RegistryMessageModel&>(_rmm).get(c);
if (mr == nullptr) {
// no messages
return dfmc_Ret::NO_MSG;
}
const uint64_t ts_now = Message::getTimeMS();
// filter for unconfirmed messages
// we assume sorted
// ("reverse" iteration <.<)
auto msg_view = mr->view<Message::Components::Timestamp>();
bool valid_unsent {false};
// we search for the oldest, not too recently sent, unconfirmed message
for (auto it = msg_view.rbegin(), view_end = msg_view.rend(); it != view_end; it++) {
const Message3 msg = *it;
// require
if (!mr->all_of<
Message::Components::MessageText, // text only for now
Message::Components::ContactTo
>(msg)
) {
continue; // skip
}
// exclude
if (mr->any_of<
Message::Components::Remote::TimestampReceived // this acts like a tag, which is wrong in groups
>(msg)
) {
continue; // skip
}
if (mr->get<Message::Components::ContactTo>(msg).c != c) {
continue; // not outbound (in private)
}
valid_unsent = true;
uint64_t msg_ts = msg_view.get<Message::Components::Timestamp>(msg).ts;
if (mr->all_of<Message::Components::TimestampWritten>(msg)) {
msg_ts = mr->get<Message::Components::TimestampWritten>(msg).ts;
}
if (mr->all_of<Message::Components::LastSendAttempt>(msg)) {
const auto lsa = mr->get<Message::Components::LastSendAttempt>(msg).ts;
if (lsa > msg_ts) {
msg_ts = lsa;
}
}
if (ts_now < (msg_ts + uint64_t(_delay_retry * 1000))) {
// not time yet
continue;
}
// it is time
const auto [msg_id, _] = _t.toxFriendSendMessage(
tfe.friend_number,
(
mr->all_of<Message::Components::TagMessageIsAction>(msg)
? Tox_Message_Type::TOX_MESSAGE_TYPE_ACTION
: Tox_Message_Type::TOX_MESSAGE_TYPE_NORMAL
),
mr->get<Message::Components::MessageText>(msg).text
);
// TODO: this is ugly
mr->emplace_or_replace<Message::Components::LastSendAttempt>(msg, ts_now);
if (msg_id.has_value()) {
// tmm will pick this up for us
mr->emplace_or_replace<Message::Components::ToxFriendMessageID>(msg, msg_id.value());
} // else error
// we sent our message, no point further iterating
return dfmc_Ret::SENT_THIS_TICK;
}
if (!valid_unsent) {
// somehow cleanup lsa
mr->storage<Message::Components::LastSendAttempt>().clear();
//std::cout << "TFFOM: all sent, deleting lsa\n";
return dfmc_Ret::NO_MSG;
}
return dfmc_Ret::TOO_SOON;
}
bool ToxFriendFauxOfflineMessaging::onToxEvent(const Tox_Event_Friend_Connection_Status* e) {
const auto friend_number = tox_event_friend_connection_status_get_friend_number(e);
const auto friend_status = tox_event_friend_connection_status_get_connection_status(e);
if (friend_status == Tox_Connection::TOX_CONNECTION_NONE) {
return false; // skip
// maybe cleanup?
}
auto c = _tcm.getContactFriend(friend_number);
if (!static_cast<bool>(c) || !c.all_of<Contact::Components::ToxFriendEphemeral, Contact::Components::ConnectionState>()) {
// UH error??
return false;
}
_cr.emplace_or_replace<Contact::Components::NextSendAttempt>(c, Message::getTimeMS() + uint64_t(_delay_after_cc*1000)); // wait before first message is sent
_interval_timer = 0.f;
return false;
}

View File

@ -0,0 +1,55 @@
#pragma once
#include <solanaceae/toxcore/tox_event_interface.hpp>
#include <solanaceae/tox_contacts/tox_contact_model2.hpp>
#include <solanaceae/contact/contact_model3.hpp>
#include <solanaceae/message3/registry_message_model.hpp>
// fwd
struct ToxI;
namespace Contact::Components {
struct ToxFriendEphemeral;
}
// resends unconfirmed messages.
// timers get reset on connection changes, and send order is preserved.
class ToxFriendFauxOfflineMessaging : public ToxEventI {
Contact3Registry& _cr;
RegistryMessageModel& _rmm;
ToxContactModel2& _tcm;
ToxI& _t;
ToxEventProviderI& _tep;
float _interval_timer{0.f};
// TODO: increase timer?
const float _delay_after_cc {4.5f};
const float _delay_inbetween {0.3f};
const float _delay_retry {10.f}; // retry sending after 10s
public:
ToxFriendFauxOfflineMessaging(
Contact3Registry& cr,
RegistryMessageModel& rmm,
ToxContactModel2& tcm,
ToxI& t,
ToxEventProviderI& tep
);
float tick(float time_delta);
private:
enum class dfmc_Ret {
TOO_SOON,
SENT_THIS_TICK,
NO_MSG,
};
// only called for online friends
// returns true if a message was sent
// dont call this too often
dfmc_Ret doFriendMessageCheck(const Contact3 c, const Contact::Components::ToxFriendEphemeral& tfe);
protected:
bool onToxEvent(const Tox_Event_Friend_Connection_Status* e) override;
};