/* * InfiniPaint * Copyright (C) 2025-2026 Yousef Khadadeh * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include "Helpers/Networking/NetLibrary.hpp" #include "NetObjWeakPtr.hpp" #include "cereal/archives/portable_binary.hpp" #include namespace NetworkingObjects { class DelayUpdateSerializedClassManager { public: template struct CustomConstructors { std::function writeConstructor = [](const T& o, cereal::PortableBinaryOutputArchive& a) { a(o); }; std::function& c)> readConstructor = [](T& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr& c) { a(o); }; std::function writeUpdate = [](const T& o, cereal::PortableBinaryOutputArchive& a) { a(o); }; std::function& c)> readUpdate = [](T& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr& c) { a(o); }; std::function(const T& o)> allocateCopy = [](const T& o) { return std::make_shared(); }; std::function assignmentFunc = [](T& o, const T& o2) { constexpr bool hasDefaultAssignment = requires(T& t, const T& t2) { t = t2; }; if constexpr(hasDefaultAssignment) o = o2; }; std::function postUpdateFunc = [](T& o) {}; }; template void register_class(NetObjManager& objMan, const CustomConstructors& t = CustomConstructors()) { objMan.register_class({ .writeConstructorFuncClient = [writeConstructor = t.writeConstructor](const NetObjTemporaryPtr& o, cereal::PortableBinaryOutputArchive& a) {writeConstructor(*o, a);}, .readConstructorFuncClient = [readConstructor = t.readConstructor](const NetObjTemporaryPtr& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr&) {readConstructor(*o, a, nullptr);}, .readUpdateFuncClient = [&, readUpdate = t.readUpdate](const NetObjTemporaryPtr& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr& c){client_read_update_func(o, a, readUpdate);}, .writeConstructorFuncServer = [writeConstructor = t.writeConstructor](const NetObjTemporaryPtr& o, cereal::PortableBinaryOutputArchive& a) {writeConstructor(*o, a);}, .readConstructorFuncServer = [readConstructor = t.readConstructor](const NetObjTemporaryPtr& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr& c) {readConstructor(*o, a, c);}, .readUpdateFuncServer = [&, readUpdate = t.readUpdate](const NetObjTemporaryPtr& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr& c){server_read_update_func(o, a, readUpdate, c);} }); typeIndexFuncs[std::type_index(typeid(T))] = { .writeUpdateFunc = [writeFunc = t.writeUpdate](const void* o, cereal::PortableBinaryOutputArchive& a) { writeFunc(*static_cast(o), a); }, .assignmentFunc = [assignFunc = t.assignmentFunc](void* o, const void* o2) { assignFunc(*static_cast(o), *static_cast(o2)); }, .postUpdateFunc = [postUpdateFunc = t.postUpdateFunc](void* o) { postUpdateFunc(*static_cast(o)); }, .allocateCopyFunc = [allocateCopyFunc = t.allocateCopy](const void* o) { return std::static_pointer_cast(allocateCopyFunc(*static_cast(o))); } }; } template void send_update_to_all(const NetworkingObjects::NetObjTemporaryPtr& o, bool finalUpdate) { // TODO: When finalUpdate = false, maybe add a small delay between updates instead of sending them every frame? o.send_update_to_all(finalUpdate ? RELIABLE_COMMAND_CHANNEL : UNRELIABLE_COMMAND_CHANNEL, [&](const NetObjTemporaryPtr& o, cereal::PortableBinaryOutputArchive& a) { if(o.get_obj_man()->is_server()) server_write_func(o, a, finalUpdate); else client_write_update_func(o, a, finalUpdate); }); } void update(NetworkingObjects::NetObjManager& netObjMan); private: template void client_write_update_func(const NetObjTemporaryPtr& o, cereal::PortableBinaryOutputArchive& a, bool finalUpdate) { a(finalUpdate); typeIndexFuncs[std::type_index(typeid(T))].writeUpdateFunc(static_cast(o.get()), a); auto it = updatingObjs.find(o.get_net_id()); if(it == updatingObjs.end()) { UpdatingObject toAdd; toAdd.objToUpdateCurrentData = typeIndexFuncs[std::type_index(typeid(T))].allocateCopyFunc(static_cast(o.get())); toAdd.doLastAssignment = std::make_shared(true); toAdd.lastUpdateTimePoint = std::chrono::steady_clock::now(); toAdd.setDataAfterTimeout = [&typeIndexFuncs = typeIndexFuncs, oWeak = NetObjWeakPtr(o), currentData = toAdd.objToUpdateCurrentData, doLastAssignment = toAdd.doLastAssignment]() { NetObjTemporaryPtr oLock = oWeak.lock(); if(oLock && *doLastAssignment) { typeIndexFuncs[std::type_index(typeid(T))].assignmentFunc(static_cast(oLock.get()), static_cast(currentData.get())); typeIndexFuncs[std::type_index(typeid(T))].postUpdateFunc(static_cast(oLock.get())); } }; toAdd.sendFinalUpdate = [&typeIndexFuncs = typeIndexFuncs, oWeak = NetObjWeakPtr(o)]() { NetObjTemporaryPtr oLock = oWeak.lock(); if(oLock) { oLock.send_update_to_all(RELIABLE_COMMAND_CHANNEL, [&typeIndexFuncs = typeIndexFuncs](const NetObjTemporaryPtr& o, cereal::PortableBinaryOutputArchive& a) { a(true); // It's the final update (sent over reliable channel) typeIndexFuncs[std::type_index(typeid(T))].writeUpdateFunc(static_cast(o.get()), a); }); } }; toAdd.lastTemporaryUpdateTimePoint = finalUpdate ? std::nullopt : std::optional(std::chrono::steady_clock::now()); updatingObjs.emplace(o.get_net_id(), toAdd); } else { auto& [netID, updatingObjData] = *it; updatingObjData.lastUpdateTimePoint = std::chrono::steady_clock::now(); updatingObjData.lastTemporaryUpdateTimePoint = finalUpdate ? std::nullopt : std::optional(std::chrono::steady_clock::now()); } } template void client_read_update_func(const NetObjTemporaryPtr& o, cereal::PortableBinaryInputArchive& a, const std::function&)>& readUpdate) { auto it = updatingObjs.find(o.get_net_id()); if(it == updatingObjs.end()) { readUpdate(*o, a, nullptr); typeIndexFuncs[std::type_index(typeid(T))].postUpdateFunc(static_cast(o.get())); } else { auto& [netID, updatingObjData] = *it; readUpdate(*std::static_pointer_cast(updatingObjData.objToUpdateCurrentData), a, nullptr); *updatingObjData.doLastAssignment = true; } } template void server_write_func(const NetObjTemporaryPtr& o, cereal::PortableBinaryOutputArchive& a, bool finalUpdate) { typeIndexFuncs[std::type_index(typeid(T))].writeUpdateFunc(static_cast(o.get()), a); auto it = updatingObjs.find(o.get_net_id()); if(it == updatingObjs.end()) { UpdatingObject toAdd; toAdd.objToUpdateCurrentData = typeIndexFuncs[std::type_index(typeid(T))].allocateCopyFunc(static_cast(o.get())); toAdd.lastUpdateTimePoint = std::chrono::steady_clock::now(); toAdd.doLastAssignment = std::make_shared(false); toAdd.setDataAfterTimeout = [&typeIndexFuncs = typeIndexFuncs, oWeak = NetObjWeakPtr(o), currentData = toAdd.objToUpdateCurrentData, doLastAssignment = toAdd.doLastAssignment]() { NetObjTemporaryPtr oLock = oWeak.lock(); if(oLock && *doLastAssignment) { typeIndexFuncs[std::type_index(typeid(T))].assignmentFunc(static_cast(oLock.get()), static_cast(currentData.get())); typeIndexFuncs[std::type_index(typeid(T))].postUpdateFunc(static_cast(oLock.get())); oLock.send_server_update_to_all_clients(RELIABLE_COMMAND_CHANNEL, [&typeIndexFuncs = typeIndexFuncs](const NetObjTemporaryPtr& o, cereal::PortableBinaryOutputArchive& a) { typeIndexFuncs[std::type_index(typeid(T))].writeUpdateFunc(static_cast(o.get()), a); }); } }; toAdd.sendFinalUpdate = [&typeIndexFuncs = typeIndexFuncs, oWeak = NetObjWeakPtr(o)]() { NetObjTemporaryPtr oLock = oWeak.lock(); if(oLock) { oLock.send_update_to_all(RELIABLE_COMMAND_CHANNEL, [&typeIndexFuncs = typeIndexFuncs](const NetObjTemporaryPtr& o, cereal::PortableBinaryOutputArchive& a) { typeIndexFuncs[std::type_index(typeid(T))].writeUpdateFunc(static_cast(o.get()), a); }); } }; toAdd.lastTemporaryUpdateTimePoint = finalUpdate ? std::nullopt : std::optional(std::chrono::steady_clock::now()); updatingObjs.emplace(o.get_net_id(), toAdd); } else { auto& [netID, updatingObjData] = *it; updatingObjData.lastUpdateTimePoint = std::chrono::steady_clock::now(); updatingObjData.lastTemporaryUpdateTimePoint = finalUpdate ? std::nullopt : std::optional(std::chrono::steady_clock::now()); typeIndexFuncs[std::type_index(typeid(T))].assignmentFunc(static_cast(updatingObjs[o.get_net_id()].objToUpdateCurrentData.get()), static_cast(o.get())); *updatingObjData.doLastAssignment = false; } } template void server_read_update_func(const NetObjTemporaryPtr& o, cereal::PortableBinaryInputArchive& a, const std::function&)>& readUpdate, const std::shared_ptr& c) { auto it = updatingObjs.find(o.get_net_id()); bool finalUpdate; if(it == updatingObjs.end()) { a(finalUpdate); readUpdate(*o, a, c); typeIndexFuncs[std::type_index(typeid(T))].postUpdateFunc(static_cast(o.get())); o.send_server_update_to_all_clients(finalUpdate ? RELIABLE_COMMAND_CHANNEL : UNRELIABLE_COMMAND_CHANNEL, [&typeIndexFuncs = typeIndexFuncs](const NetObjTemporaryPtr& o, cereal::PortableBinaryOutputArchive& a) { typeIndexFuncs[std::type_index(typeid(T))].writeUpdateFunc(static_cast(o.get()), a); }); } else { auto& [netID, updatingObjData] = *it; a(finalUpdate); readUpdate(*std::static_pointer_cast(updatingObjData.objToUpdateCurrentData), a, c); *updatingObjData.doLastAssignment = true; } } struct UpdatingObject { std::shared_ptr objToUpdateCurrentData; std::shared_ptr doLastAssignment; std::chrono::steady_clock::time_point lastUpdateTimePoint; std::function setDataAfterTimeout; std::optional lastTemporaryUpdateTimePoint; std::function sendFinalUpdate; }; std::unordered_map updatingObjs; struct ExtraFunctions { std::function writeUpdateFunc; std::function assignmentFunc; std::function postUpdateFunc; std::function(const void*)> allocateCopyFunc; }; std::unordered_map typeIndexFuncs; }; }