/*
* 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;
};
}