Fixed issues with scale up and erasing over network

This commit is contained in:
Yousef Khadadeh 2026-05-30 16:28:22 +03:00
parent 09f29c6329
commit 015e9bc5df
9 changed files with 62 additions and 20 deletions

View file

@ -22,13 +22,14 @@
namespace NetworkingObjects {
void DelayUpdateSerializedClassManager::update(NetworkingObjects::NetObjManager& netObjMan) {
for(auto& [objID, updatingObj] : updatingObjs) {
if(updatingObj.lastTemporaryUpdateTimePoint.has_value() && (std::chrono::steady_clock::now() - updatingObj.lastTemporaryUpdateTimePoint.value()) >= std::chrono::milliseconds(300)) {
if(!updatingObj.updateLock && updatingObj.lastTemporaryUpdateTimePoint.has_value() && (std::chrono::steady_clock::now() - updatingObj.lastTemporaryUpdateTimePoint.value()) >= std::chrono::milliseconds(300)) {
updatingObj.sendFinalUpdate();
updatingObj.lastTemporaryUpdateTimePoint = std::nullopt;
}
}
std::erase_if(updatingObjs, [](auto& updatingPair) {
if((std::chrono::steady_clock::now() - updatingPair.second.lastUpdateTimePoint) >= std::chrono::seconds(3) && !updatingPair.second.lastTemporaryUpdateTimePoint.has_value()) {
constexpr auto TIME_TO_DISPLAY_NETWORK_UPDATES = std::chrono::seconds(5);
if((std::chrono::steady_clock::now() - updatingPair.second.lastUpdateTimePoint) >= TIME_TO_DISPLAY_NETWORK_UPDATES && !updatingPair.second.lastTemporaryUpdateTimePoint.has_value() && !updatingPair.second.updateLock) {
updatingPair.second.setDataAfterTimeout();
return true;
}

View file

@ -24,6 +24,16 @@
namespace NetworkingObjects {
class DelayUpdateSerializedClassManager {
private:
struct UpdatingObject {
std::shared_ptr<void> objToUpdateCurrentData;
std::shared_ptr<bool> doLastAssignment;
std::chrono::steady_clock::time_point lastUpdateTimePoint;
std::function<void()> setDataAfterTimeout;
std::optional<std::chrono::steady_clock::time_point> lastTemporaryUpdateTimePoint;
std::function<void()> sendFinalUpdate;
bool updateLock = false;
};
public:
template <typename T> struct CustomConstructors {
std::function<void(const T& o, cereal::PortableBinaryOutputArchive& a)> writeConstructor = [](const T& o, cereal::PortableBinaryOutputArchive& a) {
@ -83,13 +93,27 @@ namespace NetworkingObjects {
client_write_update_func(o, a, finalUpdate);
});
}
// NOTE: Locked objects will not be erased from the DelayUpdateSerializedClassManager map. Make sure you unlock objects before they're erased
template <typename T> void set_object_update_lock(const NetworkingObjects::NetObjTemporaryPtr<T>& o, bool lock) {
if(o.get_obj_man()->is_connected()) {
UpdatingObject* updatingObj;
if(o.get_obj_man()->is_server())
updatingObj = server_create_updating_obj_entry(o, false);
else
updatingObj = client_create_updating_obj_entry(o, false);
updatingObj->updateLock = lock;
}
}
void update(NetworkingObjects::NetObjManager& netObjMan);
private:
template <typename T> void client_write_update_func(const NetObjTemporaryPtr<T>& o, cereal::PortableBinaryOutputArchive& a, bool finalUpdate) {
a(finalUpdate);
typeIndexFuncs[std::type_index(typeid(T))].writeUpdateFunc(static_cast<const void*>(o.get()), a);
auto it = updatingObjs.find(o.get_net_id());
client_create_updating_obj_entry(o, finalUpdate);
}
template <typename T> UpdatingObject* client_create_updating_obj_entry(const NetObjTemporaryPtr<T>& o, bool finalUpdate) {
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<const void*>(o.get()));
@ -112,13 +136,14 @@ namespace NetworkingObjects {
}
};
toAdd.lastTemporaryUpdateTimePoint = finalUpdate ? std::nullopt : std::optional<std::chrono::steady_clock::time_point>(std::chrono::steady_clock::now());
updatingObjs.emplace(o.get_net_id(), toAdd);
it = updatingObjs.emplace(o.get_net_id(), toAdd).first;
}
else {
auto& [netID, updatingObjData] = *it;
updatingObjData.lastUpdateTimePoint = std::chrono::steady_clock::now();
updatingObjData.lastTemporaryUpdateTimePoint = finalUpdate ? std::nullopt : std::optional<std::chrono::steady_clock::time_point>(std::chrono::steady_clock::now());
}
return &it->second;
}
template <typename T> void client_read_update_func(const NetObjTemporaryPtr<T>& o, cereal::PortableBinaryInputArchive& a, const std::function<void(T&, cereal::PortableBinaryInputArchive&, const std::shared_ptr<NetServer::ClientData>&)>& readUpdate) {
@ -136,6 +161,10 @@ namespace NetworkingObjects {
template <typename T> void server_write_func(const NetObjTemporaryPtr<T>& o, cereal::PortableBinaryOutputArchive& a, bool finalUpdate) {
typeIndexFuncs[std::type_index(typeid(T))].writeUpdateFunc(static_cast<const void*>(o.get()), a);
server_create_updating_obj_entry(o, finalUpdate);
}
template <typename T> UpdatingObject* server_create_updating_obj_entry(const NetObjTemporaryPtr<T>& o, bool finalUpdate) {
auto it = updatingObjs.find(o.get_net_id());
if(it == updatingObjs.end()) {
@ -162,7 +191,7 @@ namespace NetworkingObjects {
}
};
toAdd.lastTemporaryUpdateTimePoint = finalUpdate ? std::nullopt : std::optional<std::chrono::steady_clock::time_point>(std::chrono::steady_clock::now());
updatingObjs.emplace(o.get_net_id(), toAdd);
it = updatingObjs.emplace(o.get_net_id(), toAdd).first;
}
else {
auto& [netID, updatingObjData] = *it;
@ -171,6 +200,8 @@ namespace NetworkingObjects {
typeIndexFuncs[std::type_index(typeid(T))].assignmentFunc(static_cast<void*>(updatingObjs[o.get_net_id()].objToUpdateCurrentData.get()), static_cast<const void*>(o.get()));
*updatingObjData.doLastAssignment = false;
}
return &it->second;
}
template <typename T> void server_read_update_func(const NetObjTemporaryPtr<T>& o, cereal::PortableBinaryInputArchive& a, const std::function<void(T&, cereal::PortableBinaryInputArchive&, const std::shared_ptr<NetServer::ClientData>&)>& readUpdate, const std::shared_ptr<NetServer::ClientData>& c) {
@ -192,14 +223,6 @@ namespace NetworkingObjects {
}
}
struct UpdatingObject {
std::shared_ptr<void> objToUpdateCurrentData;
std::shared_ptr<bool> doLastAssignment;
std::chrono::steady_clock::time_point lastUpdateTimePoint;
std::function<void()> setDataAfterTimeout;
std::optional<std::chrono::steady_clock::time_point> lastTemporaryUpdateTimePoint;
std::function<void()> sendFinalUpdate;
};
std::unordered_map<NetObjID, UpdatingObject> updatingObjs;
struct ExtraFunctions {

View file

@ -79,6 +79,10 @@ void CanvasComponentContainer::send_comp_update(DrawingProgram& drawP, bool fina
drawP.world.delayedUpdateObjectManager.send_update_to_all<CanvasComponentAllocator>(compAllocator, finalUpdate);
}
void CanvasComponentContainer::set_object_update_lock(DrawingProgram& drawP, bool lockSet) {
drawP.world.delayedUpdateObjectManager.set_object_update_lock<CanvasComponentAllocator>(compAllocator, lockSet);
}
void CanvasComponentContainer::write_constructor_func(const NetworkingObjects::NetObjTemporaryPtr<CanvasComponentContainer>& o, cereal::PortableBinaryOutputArchive& a) {
a(o->coords);
o->compAllocator.write_create_message(a);

View file

@ -96,6 +96,7 @@ class CanvasComponentContainer {
void send_comp_update(DrawingProgram& drawP, bool finalUpdate);
void scale_up(const WorldScalar& scaleUpAmount);
void normalize_object_coordinates();
void set_object_update_lock(DrawingProgram& drawP, bool lockSet);
std::weak_ptr<DrawingProgramCacheBVHNode> cacheParentBvhNode;
DrawingProgramLayerListItem* parentLayer = nullptr;

View file

@ -300,11 +300,11 @@ void DrawingProgram::server_init_no_file() {
void DrawingProgram::scale_up(const WorldScalar& scaleUpAmount) {
selection.deselect_all();
switch_to_tool(drawTool->get_type() == DrawingProgramToolType::GRIDMODIFY ? DrawingProgramToolType::EDIT : drawTool->get_type(), true);
layerMan.scale_up(scaleUpAmount);
if(controls.lockedCameraScale.has_value())
controls.lockedCameraScale.value() *= scaleUpAmount;
rebuild_cache();
switch_to_tool(drawTool->get_type() == DrawingProgramToolType::GRIDMODIFY ? DrawingProgramToolType::EDIT : drawTool->get_type(), true);
}
void DrawingProgram::write_components_server(cereal::PortableBinaryOutputArchive& a) {

View file

@ -88,4 +88,8 @@ bool EditTransformCanvasComponentWorldUndoAction::undo_redo(WorldUndoManager& un
return true;
}
void EditTransformCanvasComponentWorldUndoAction::scale_up(const WorldScalar& scaleAmount) {
coords.scale_about(WorldVec{0, 0}, scaleAmount, true);
}
EditTransformCanvasComponentWorldUndoAction::~EditTransformCanvasComponentWorldUndoAction() {}

View file

@ -39,6 +39,7 @@ class EditTransformCanvasComponentWorldUndoAction : public WorldUndoAction {
bool undo(WorldUndoManager& undoMan) override;
bool redo(WorldUndoManager& undoMan) override;
bool undo_redo(WorldUndoManager& undoMan);
void scale_up(const WorldScalar& scaleAmount) override;
~EditTransformCanvasComponentWorldUndoAction();
std::unique_ptr<CanvasComponent> data;

View file

@ -141,6 +141,7 @@ void EraserTool::erase_on_path() {
auto it = updatedComponents.find(c);
if(it != updatedComponents.end()) {
c->obj->get_comp().set_data_from(*it->second.copyData->obj);
c->obj->set_object_update_lock(drawP, false);
updatedComponents.erase(it);
}
erasedComponents.emplace(c);
@ -161,8 +162,10 @@ void EraserTool::erase_on_path() {
case CanvasComponentEraseDetailResult::NO_CHANGE:
return false;
case CanvasComponentEraseDetailResult::CHANGED: {
if(!updatedComponents.contains(c))
if(!updatedComponents.contains(c)) {
c->obj->set_object_update_lock(drawP, true);
updatedComponents.emplace(c, std::move(dataCopy));
}
// NOTE: commit_update is what should be run here, but invalidate_cache is being run instead. This is for a few reasons:
// - Mesh initialize_draw_data does nothing, so commit_update isnt necessary
// - worldAABB only shrinks, which means that invalidate_cache_at_optional_aabb will invalidate a "good enough" space even if worldAABB isn't updated (which commit_update does)
@ -171,20 +174,23 @@ void EraserTool::erase_on_path() {
drawP.drawCache.invalidate_cache_at_optional_aabb(c->obj->get_world_bounds());
return false;
}
case CanvasComponentEraseDetailResult::REMOVED:
case CanvasComponentEraseDetailResult::REMOVED: {
auto it = updatedComponents.find(c);
if(it != updatedComponents.end()) {
c->obj->get_comp().set_data_from(*it->second.copyData->obj);
c->obj->set_object_update_lock(drawP, false);
updatedComponents.erase(it);
}
else
c->obj->get_comp().set_data_from(*dataCopy->obj);
erasedComponents.emplace(c);
drawP.drawCache.invalidate_cache_at_optional_aabb(c->obj->get_world_bounds());
return true;
}
}
}
}
else {
if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector) && c->obj->collides_with(genData.coords, erasePath)) {
erasedComponents.emplace(c);
@ -211,6 +217,7 @@ void EraserTool::reset_erasing_stroke() {
void EraserTool::erase_component(CanvasComponentContainer::ObjInfo* erasedComp) {
erasedComponents.erase(erasedComp);
updatedComponents.erase(erasedComp);
erasedComp->obj->set_object_update_lock(drawP, false);
}
void EraserTool::commit_erase() {
@ -223,6 +230,7 @@ void EraserTool::commit_erase() {
std::erase_if(updatedComponents, [&] (auto& p) {
auto& [comp, oldData] = p;
comp->obj->set_object_update_lock(drawP, false);
if(!oldData.splitComps.empty()) {
for(CanvasComponentContainer* c : oldData.splitComps)
newObjectsToPlace[comp->obj->parentLayer].emplace_back(std::next(comp->obj->objInfo), c);

View file

@ -628,13 +628,13 @@ void World::scale_up_step() {
void World::scale_up(const WorldScalar& scaleUpAmount) {
Logger::get().log(Logger::LogType::DESKTOP_USERINFO, "Canvas scaled up");
// drawProg will be sending info on committed objects/modified grids. These objects will NOT be scaled up.
// This means that the scale up message must be sent AFTER the World::scale_up function is called on our end
// if this client is the one responsible for the scale up
drawProg.scale_up(scaleUpAmount);
bMan.scale_up(scaleUpAmount);
gridMan.scale_up(scaleUpAmount);
drawData.cam.scale_up(*this, scaleUpAmount);
// drawProg will be sending info on committed objects/modified grids. These objects will be scaled up already.
// This means that the scale up message must be send BEFORE the World::scale_up function is called on our end
// if this client is the one responsible for the scale up
drawProg.scale_up(scaleUpAmount);
undo.scale_up(scaleUpAmount);
}