Compare commits

...

13 Commits

Author SHA1 Message Date
cff0c100ec add fragment store draft doc 2024-02-11 19:01:48 +01:00
010c49d100 stable names 2024-02-10 12:23:40 +01:00
ff5dbaffc0 fix some file selector glitches 2024-02-08 18:11:51 +01:00
b56d581e4b fix normal feeling sluggish 2024-02-05 16:15:10 +01:00
aa661aaaa7 default to normal fps mode again 2024-02-05 16:10:30 +01:00
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
17 changed files with 364 additions and 93 deletions

View File

@ -37,7 +37,7 @@ namespace 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);
}
@ -121,8 +121,10 @@ ChatGui4::ChatGui4(
ConfigModelI& conf,
RegistryMessageModel& rmm,
Contact3Registry& cr,
TextureUploaderI& tu
) : _conf(conf), _rmm(rmm), _cr(cr), _tal(_cr), _contact_tc(_tal, tu), _msg_tc(_mil, tu), _sip(tu) {
TextureUploaderI& 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) {
@ -136,20 +138,7 @@ ChatGui4::~ChatGui4(void) {
//}
}
void 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();
float ChatGui4::render(float time_delta) {
_fss.render();
_sip.render(time_delta);
@ -627,8 +616,7 @@ void ChatGui4::render(float time_delta) {
}
ImGui::End();
_contact_tc.workLoadQueue();
_msg_tc.workLoadQueue();
return 1000.f; // TODO: higher min fps?
}
void ChatGui4::sendFilePath(const char* file_path) {
@ -749,7 +737,11 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) {
) {
if (ImGui::Button("save to")) {
_fss.requestFile(
[](const auto& path) -> bool { return std::filesystem::is_directory(path); },
[](std::filesystem::path& path) -> bool {
// remove file path
path.remove_filename();
return std::filesystem::is_directory(path);
},
[this, &reg, e](const auto& path) {
if (reg.valid(e)) { // still valid
// TODO: trim file?

View File

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

View File

@ -18,7 +18,7 @@ FileSelector::FileSelector(void) {
}
void FileSelector::requestFile(
std::function<bool(const std::filesystem::path& path)>&& is_valid,
std::function<bool(std::filesystem::path& path)>&& is_valid,
std::function<void(const std::filesystem::path& path)>&& on_choose,
std::function<void(void)>&& on_cancel
) {
@ -77,7 +77,9 @@ void FileSelector::render(void) {
if (current_path.has_parent_path()) {
if (ImGui::TableNextColumn()) {
if (ImGui::Selectable("D##..", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
_current_file_path = _current_file_path.parent_path();
// the first "parent_path()" only removes the filename and the ending "/"
_current_file_path = _current_file_path.parent_path().parent_path() / "";
//_current_file_path = _current_file_path.remove_filename().parent_path() / "";
}
}

View File

@ -8,7 +8,7 @@ struct FileSelector {
bool _open_popup {false};
std::function<bool(const std::filesystem::path& path)> _is_valid = [](auto){ return true; };
std::function<bool(std::filesystem::path& path)> _is_valid = [](auto){ return true; };
std::function<void(const std::filesystem::path& path)> _on_choose = [](auto){};
std::function<void(void)> _on_cancel = [](){};
@ -18,8 +18,9 @@ struct FileSelector {
FileSelector(void);
// TODO: supply hints
// HACK: until we supply hints, is_valid can modify
void requestFile(
std::function<bool(const std::filesystem::path& path)>&& is_valid,
std::function<bool(std::filesystem::path& path)>&& is_valid,
std::function<void(const std::filesystem::path& path)>&& on_choose,
std::function<void(void)>&& on_cancel
);

View File

@ -0,0 +1,72 @@
# Fragment Store
Fragments are are pieces of information split into Metadata and Data.
They can be stored seperated or together.
They can be used as a Transport protocol/logic too.
# Store types
### Object Store
Fragment files are stored with the first 2 hex chars as sub folders:
eg:
objects/ (object store root)
- 5f/ (first 2hex subfolder)
- 4fffffff (the fragment file without the first 2 hexchars)
### Split Object Store
Same as Object Store, but medadata and data stored in seperate files.
Metadata files have the `.meta` suffix. They also have a filetype specific suffix, like `.json`, `.msgpack` etc.
### Memory Store
Just keeps the Fragments in memory.
# File formats
Files can be compressed and encrypted. Since compression needs the data structure to funcion, it is applied before it is encrypted.
### Text Json
Text json only makes sense for metadata if it's neither compressed nor encrypted. (otherwise its binary on disk anyway, so why waste bytes).
Since the content of data is not looked at, nothing stops you from using text json and ecrypt it, but atleast basic compression is advised.
A Metadata json object has the following keys:
- `enc` Encryption type of the data, if any
- `comp` Compression type of the data, if any
## Binary file headers
### Split Metadata
file magic bytes `SOLMET` (6 bytes)
1 byte encryption type (0x00 is none)
1 byte compression type (0x00 is none)
...metadata here...
note that the ecnryption and compression are for the metadata only.
The metadata itself contains encryption and compression about the data.
### Split Data
(none) all the data is in the metadata file.
This is mostly to allow direct storage for files in the Fragment store without excessive duplication.
Keep in mind to not use the actual file name as the data/meta file name.
### Single fragment
file magic bytes `SOLFIL` (6 bytes)
1 byte encryption type (0x00 is none)
1 byte compression type (0x00 is none)
...metadata here...
...data here...

View File

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

View File

@ -1,10 +1,13 @@
#include "./main_screen.hpp"
#include <solanaceae/contact/components.hpp>
#include <imgui/imgui.h>
#include <SDL3/SDL.h>
#include <memory>
#include <cmath>
MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path, std::string save_password, std::vector<std::string> plugins) :
renderer(renderer_),
@ -20,7 +23,11 @@ MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path, std::stri
mmil(rmm),
tam(rmm, cr, conf),
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),
tuiu(tc, conf),
tdch(tpi)
@ -167,7 +174,22 @@ Screen* MainScreen::render(float time_delta, bool&) {
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
tuiu.render(); // render
tdch.render(); // render
@ -216,20 +238,136 @@ Screen* MainScreen::render(float time_delta, bool&) {
ImGui::ShowDemoWindow();
}
if (
_fps_perf_mode > 1 // TODO: magic
) {
// powersave forces 250ms
_render_interval = 1.f/4.f;
} else if (
_time_since_event > 1.f && ( // 1sec cool down
_fps_perf_mode == 1 || // TODO: magic
_window_hidden
float tc_unfinished_queue_interval;
{ // load rendered but not loaded textures
bool unfinished_work_queue = contact_tc.workLoadQueue();
unfinished_work_queue = unfinished_work_queue || msg_tc.workLoadQueue();
if (unfinished_work_queue) {
tc_unfinished_queue_interval = 0.1f; // so we can get images loaded faster
} else {
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/60.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/60.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 {
_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;
@ -253,7 +391,9 @@ Screen* MainScreen::tick(float time_delta, bool& quit) {
mts.iterate(); // compute
_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
);
_min_tick_interval = std::min<float>(
@ -261,6 +401,8 @@ Screen* MainScreen::tick(float time_delta, bool& quit) {
fo_interval
);
//std::cout << "MS: min tick interval: " << _min_tick_interval << "\n";
switch (_compute_perf_mode) {
// normal 1ms lower bound
case 0: _min_tick_interval = std::max<float>(_min_tick_interval, 0.001f); break;

View File

@ -21,6 +21,10 @@
#include "./tox_avatar_manager.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 "./settings_window.hpp"
#include "./tox_ui_utils.hpp"
@ -61,6 +65,11 @@ struct MainScreen final : public Screen {
SDLRendererTextureUploader sdlrtu;
//OpenGLTextureUploader ogltu;
ToxAvatarLoader tal;
TextureCache<void*, Contact3, ToxAvatarLoader> contact_tc;
MessageImageLoader mil;
TextureCache<void*, Message3Handle, MessageImageLoader> msg_tc;
ChatGui4 cg;
SettingsWindow sw;
ToxUIUtils tuiu;
@ -69,7 +78,7 @@ struct MainScreen final : public Screen {
bool _show_tool_style_editor {false};
bool _window_hidden {false};
bool _window_hidden_ts {0};
uint64_t _window_hidden_ts {0};
float _time_since_event {0.f};
MainScreen(SDL_Renderer* renderer_, std::string save_path, std::string save_password, std::vector<std::string> plugins);
@ -85,7 +94,7 @@ struct MainScreen final : public Screen {
// 0 - normal
// 1 - reduced
// 2 - power save
int _fps_perf_mode {1};
int _fps_perf_mode {0};
// 0 - normal
// 1 - power save
int _compute_perf_mode {0};

View File

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

View File

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

View File

@ -10,6 +10,8 @@
#include <limits>
#include <cstdint>
//#include <iostream>
namespace Message::Components {
struct LastSendAttempt {
uint64_t ts {0};
@ -29,15 +31,17 @@ ToxFriendFauxOfflineMessaging::ToxFriendFauxOfflineMessaging(
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) {
// hard limit interval to once per minute
_interval_timer += time_delta;
if (_interval_timer < 1.f * 60.f) {
return std::max(60.f - _interval_timer, 0.001f); // TODO: min next timer
_interval_timer -= time_delta;
if (_interval_timer > 0.f) {
return std::max(_interval_timer, 0.001f); // TODO: min next timer
}
_interval_timer = 0.f;
// interval ~ once per minute
_interval_timer = 60.f;
const uint64_t ts_now = Message::getTimeMS();
@ -50,37 +54,47 @@ float ToxFriendFauxOfflineMessaging::tick(float time_delta) {
// 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)) {
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& next_attempt = _cr.get<Contact::Components::NextSendAttempt>(c).ts;
if (doFriendMessageCheck(c, tfe)) {
next_attempt = ts_now + uint64_t(_delay_inbetween*1000);
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);
}
min_next_attempt_ts = std::min(min_next_attempt_ts, next_attempt);
}
}
});
if (min_next_attempt_ts <= ts_now) {
// we (probably) sent this iterate
_interval_timer = 60.f - 0.1f; // TODO: ugly magic
return 0.1f;
_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
return 60.f; // TODO: ugly magic
} else {
// TODO: ugly magic
return _interval_timer = 60.f - std::min(60.f, (min_next_attempt_ts - ts_now) / 1000.f);
_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;
}
bool ToxFriendFauxOfflineMessaging::doFriendMessageCheck(const Contact3 c, const Contact::Components::ToxFriendEphemeral& tfe) {
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)
@ -88,7 +102,7 @@ bool ToxFriendFauxOfflineMessaging::doFriendMessageCheck(const Contact3 c, const
auto* mr = static_cast<const RegistryMessageModel&>(_rmm).get(c);
if (mr == nullptr) {
// no messages
return false;
return dfmc_Ret::NO_MSG;
}
const uint64_t ts_now = Message::getTimeMS();
@ -98,6 +112,7 @@ bool ToxFriendFauxOfflineMessaging::doFriendMessageCheck(const Contact3 c, const
// 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;
@ -119,6 +134,12 @@ bool ToxFriendFauxOfflineMessaging::doFriendMessageCheck(const Contact3 c, const
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;
@ -155,12 +176,17 @@ bool ToxFriendFauxOfflineMessaging::doFriendMessageCheck(const Contact3 c, const
} // else error
// we sent our message, no point further iterating
return true;
return dfmc_Ret::SENT_THIS_TICK;
}
// TODO: somehow cleanup lsa
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 false;
return dfmc_Ret::TOO_SOON;
}
bool ToxFriendFauxOfflineMessaging::onToxEvent(const Tox_Event_Friend_Connection_Status* e) {
@ -180,8 +206,7 @@ bool ToxFriendFauxOfflineMessaging::onToxEvent(const Tox_Event_Friend_Connection
_cr.emplace_or_replace<Contact::Components::NextSendAttempt>(c, Message::getTimeMS() + uint64_t(_delay_after_cc*1000)); // wait before first message is sent
// TODO: ugly magic
_interval_timer = 60.f - 0.1f;
_interval_timer = 0.f;
return false;
}

View File

@ -39,10 +39,15 @@ class ToxFriendFauxOfflineMessaging : public ToxEventI {
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
bool doFriendMessageCheck(const Contact3 c, const Contact::Components::ToxFriendEphemeral& tfe);
dfmc_Ret doFriendMessageCheck(const Contact3 c, const Contact::Components::ToxFriendEphemeral& tfe);
protected:
bool onToxEvent(const Tox_Event_Friend_Connection_Status* e) override;