Transformations over network work again, with undo and redo

This commit is contained in:
Yousef Khadadeh 2025-07-27 11:56:34 +03:00
parent b6a25280e6
commit 7e69688ca6
11 changed files with 128 additions and 33 deletions

View file

@ -5,7 +5,7 @@
#include <poolstl.hpp>
#endif
template <typename ContainerType> void parallel_loop_container(ContainerType& c, std::function<void(typename ContainerType::value_type&)> func) {
template <typename ContainerType> void parallel_loop_container(const ContainerType& c, std::function<void(const typename ContainerType::value_type&)> func) {
#ifdef __EMSCRIPTEN__
std::for_each(c.begin(), c.end(), func);
#else

View file

@ -153,6 +153,14 @@ CoordSpaceHelper CoordSpaceHelper::other_coord_space_to_this_space(const CoordSp
return toRet;
}
bool CoordSpaceHelper::operator==(const CoordSpaceHelper& otherCoords) const {
return rotation == otherCoords.rotation && pos == otherCoords.pos && inverseScale == otherCoords.inverseScale;
}
bool CoordSpaceHelper::operator!=(const CoordSpaceHelper& otherCoords) const {
return rotation != otherCoords.rotation || pos != otherCoords.pos || inverseScale != otherCoords.inverseScale;
}
CoordSpaceHelper CoordSpaceHelper::other_coord_space_from_this_space(const CoordSpaceHelper& other) const {
CoordSpaceHelper toRet;
toRet.pos = from_space_world(other.pos);

View file

@ -24,8 +24,10 @@ class CoordSpaceHelper {
CoordSpaceHelper();
CoordSpaceHelper(const WorldVec& initPos, const WorldScalar& initInverseScale, double initRotation);
Vector2f to_space(const WorldVec& coord) const;
bool operator==(const CoordSpaceHelper& otherCoords) const;
bool operator!=(const CoordSpaceHelper& otherCoords) const;
Vector2f to_space(const WorldVec& coord) const;
WorldVec from_space(Vector2f coord) const;

View file

@ -83,8 +83,8 @@ void DrawComponent::server_send_update_final(MainServer& server, ServerClientID
serverIsTempUpdate = false;
}
void DrawComponent::server_send_transform(MainServer& server, ServerClientID id) {
server.netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_TRANSFORM_COMPONENT, id, this->coords);
void DrawComponent::server_send_transform_many(MainServer& server, const std::vector<std::pair<ServerClientID, CoordSpaceHelper>>& transforms) {
server.netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_TRANSFORM_MANY_COMPONENTS, transforms);
}
void DrawComponent::server_update(MainServer& server, ServerClientID id) {
@ -156,9 +156,8 @@ void DrawComponent::client_send_update_final(DrawingProgram& drawP) {
drawP.world.con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_UPDATE_COMPONENT, false, collabListInfo.lock()->id, *this);
}
void DrawComponent::client_send_transform(DrawingProgram& drawP) {
if(collabListInfo.lock())
drawP.world.con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_TRANSFORM_COMPONENT, collabListInfo.lock()->id, this->coords);
void DrawComponent::client_send_transform_many(DrawingProgram& drawP, const std::vector<std::pair<ServerClientID, CoordSpaceHelper>>& transforms) {
drawP.world.con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_TRANSFORM_MANY_COMPONENTS, transforms);
}
void DrawComponent::client_send_erase(DrawingProgram& drawP) {

View file

@ -69,7 +69,7 @@ class DrawComponent {
static void server_send_erase_set(MainServer& server, const std::unordered_set<ServerClientID>& ids);
void server_send_update_temp(MainServer& server, ServerClientID id);
void server_send_update_final(MainServer& server, ServerClientID id);
void server_send_transform(MainServer& server, ServerClientID id);
static void server_send_transform_many(MainServer& server, const std::vector<std::pair<ServerClientID, CoordSpaceHelper>>& transforms);
std::chrono::time_point<std::chrono::steady_clock> tempServerUpdateTimer;
bool serverIsTempUpdate = false;
@ -94,7 +94,7 @@ class DrawComponent {
static void client_send_erase_set(DrawingProgram& drawP, const std::unordered_set<ServerClientID>& ids);
void client_send_update_temp(DrawingProgram& drawP);
void client_send_update_final(DrawingProgram& drawP);
void client_send_transform(DrawingProgram& drawP);
static void client_send_transform_many(DrawingProgram& drawP, const std::vector<std::pair<ServerClientID, CoordSpaceHelper>>& transforms);
std::optional<SCollision::AABB<WorldScalar>> worldAABB;

View file

@ -89,17 +89,38 @@ void DrawingProgram::init_client_callbacks() {
delayedUpdateComponents.emplace(comp);
}
});
world.con.client_add_recv_callback(CLIENT_TRANSFORM_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
ServerClientID id;
message(id);
auto objPtr = components.get_item_by_id(id);
if(!objPtr)
return;
if(selection.is_selected(objPtr)) // Whatever transformation we're doing right now will overwrite this transformation, so we can ignore this message
return;
std::shared_ptr<DrawComponent>& comp = objPtr->obj;
message(comp->coords);
comp->finalize_update(*this); // No need to update draw data during transformation
world.con.client_add_recv_callback(CLIENT_TRANSFORM_MANY_COMPONENTS, [&](cereal::PortableBinaryInputArchive& message) {
std::vector<std::pair<ServerClientID, CoordSpaceHelper>> transforms;
message(transforms);
if(transforms.size() >= DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD) {
std::atomic<size_t> objsActuallyTransformed = 0;
parallel_loop_container(transforms, [&](auto& p) {
auto& [id, coords] = p;
auto objPtr = components.get_item_by_id(id);
if(!objPtr || selection.is_selected(objPtr)) // Whatever transformation we're doing right now will overwrite this transformation, so we can ignore this message
return;
std::shared_ptr<DrawComponent>& comp = objPtr->obj;
if(comp->coords == coords)
return;
comp->coords = coords;
objsActuallyTransformed++;
comp->finalize_update(*this, false);
});
if(objsActuallyTransformed != 0)
force_rebuild_cache();
}
else {
for(auto& [id, coords] : transforms) {
auto objPtr = components.get_item_by_id(id);
if(!objPtr || selection.is_selected(objPtr)) // Whatever transformation we're doing right now will overwrite this transformation, so we can ignore this message
continue;
std::shared_ptr<DrawComponent>& comp = objPtr->obj;
if(comp->coords == coords)
return;
comp->coords = coords;
comp->finalize_update(*this); // No need to update draw data during transformation
}
}
});
world.con.client_add_recv_callback(CLIENT_PLACE_SINGLE_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
uint64_t insertPosition;

View file

@ -2,6 +2,7 @@
#include "DrawingProgram.hpp"
#include "../World.hpp"
#include "../MainProgram.hpp"
#include "Helpers/Parallel.hpp"
#include <chrono>
#ifdef USE_SKIA_BACKEND_GRAPHITE
@ -177,7 +178,7 @@ void DrawingProgramCache::preupdate_component(const CollabListType::ObjectInfoPt
invalidate_cache_before_pos(c->pos);
}
void DrawingProgramCache::build_bvh_node(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode, std::vector<CollabListType::ObjectInfoPtr> components) {
void DrawingProgramCache::build_bvh_node(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode, const std::vector<CollabListType::ObjectInfoPtr>& components) {
if(components.empty())
return;

View file

@ -70,7 +70,8 @@ class DrawingProgramCache {
void build(std::vector<CollabListType::ObjectInfoPtr> components);
void traverse_bvh_run_function_recursive(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode, const SCollision::AABB<WorldScalar>& aabb, std::function<bool(const std::shared_ptr<DrawingProgramCacheBVHNode>& node, const std::vector<CollabListType::ObjectInfoPtr>& components)> f);
std::pair<std::shared_ptr<DrawingProgramCacheBVHNode>, std::vector<CollabListType::ObjectInfoPtr>::iterator> get_bvh_node_fully_containing_recursive(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode, DrawComponent* c);
void build_bvh_node(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode, std::vector<CollabListType::ObjectInfoPtr> components);
void build_bvh_node(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode, const std::vector<CollabListType::ObjectInfoPtr>& components);
std::chrono::steady_clock::time_point lastBvhBuildTime;
std::shared_ptr<DrawingProgramCacheBVHNode> bvhRoot;

View file

@ -74,11 +74,74 @@ void DrawingProgramSelection::calculate_aabb() {
void DrawingProgramSelection::deselect_all() {
if(is_something_selected()) {
std::vector<CollabListType::ObjectInfoPtr> a(selectedSet.begin(), selectedSet.end());
std::vector<std::pair<CollabListType::ObjectInfoPtr, CoordSpaceHelper>> transformsFrom;
for(auto& transformedObj : a)
transformsFrom.emplace_back(transformedObj, transformedObj->obj->coords);
parallel_loop_container(a, [&](auto& obj) {
obj->obj->coords = selectionTransformCoords.other_coord_space_from_this_space(obj->obj->coords);
obj->obj->final_update(drawP, false);
obj->obj->client_send_transform(drawP);
});
std::vector<std::pair<CollabListType::ObjectInfoPtr, CoordSpaceHelper>> transformsTo;
std::vector<std::pair<ServerClientID, CoordSpaceHelper>> transformsToSend;
for(auto& transformedObj : a) {
transformsToSend.emplace_back(transformedObj->id, transformedObj->obj->coords);
transformsTo.emplace_back(transformedObj, transformedObj->obj->coords);
}
DrawComponent::client_send_transform_many(drawP, transformsToSend);
drawP.world.undo.push(UndoManager::UndoRedoPair{
[&, transformsFrom = transformsFrom]() {
std::vector<std::pair<ServerClientID, CoordSpaceHelper>> transformsToSend;
for(auto& [comp, coords] : transformsFrom) {
auto lockObjInfo = comp->obj->collabListInfo.lock();
if(lockObjInfo)
transformsToSend.emplace_back(lockObjInfo->id, coords);
else
return false;
}
parallel_loop_container(transformsFrom, [&](auto& p) {
auto& [comp, coords] = p;
comp->obj->coords = coords;
comp->obj->finalize_update(drawP, false);
});
DrawComponent::client_send_transform_many(drawP, transformsToSend);
drawP.reset_tools();
drawP.force_rebuild_cache();
return true;
},
[&, transformsTo = transformsTo]() {
std::vector<std::pair<ServerClientID, CoordSpaceHelper>> transformsToSend;
for(auto& [comp, coords] : transformsTo) {
auto lockObjInfo = comp->obj->collabListInfo.lock();
if(lockObjInfo)
transformsToSend.emplace_back(lockObjInfo->id, coords);
else
return false;
}
parallel_loop_container(transformsTo, [&](auto& p) {
auto& [comp, coords] = p;
comp->obj->coords = coords;
comp->obj->finalize_update(drawP, false);
});
DrawComponent::client_send_transform_many(drawP, transformsToSend);
drawP.reset_tools();
drawP.force_rebuild_cache();
return true;
}
});
selectedSet.clear();
cache.clear();
selectionTransformCoords = CoordSpaceHelper();

View file

@ -9,7 +9,7 @@ enum ServerCommands : MessageCommandType {
SERVER_MOVE_MOUSE,
SERVER_PLACE_SINGLE_COMPONENT,
SERVER_PLACE_MANY_COMPONENTS,
SERVER_TRANSFORM_COMPONENT,
SERVER_TRANSFORM_MANY_COMPONENTS,
SERVER_ERASE_SINGLE_COMPONENT,
SERVER_ERASE_MANY_COMPONENTS,
SERVER_UPDATE_COMPONENT,
@ -28,7 +28,7 @@ enum ClientCommands : MessageCommandType {
CLIENT_USER_DISCONNECT,
CLIENT_PLACE_SINGLE_COMPONENT,
CLIENT_PLACE_MANY_COMPONENTS,
CLIENT_TRANSFORM_COMPONENT,
CLIENT_TRANSFORM_MANY_COMPONENTS,
CLIENT_ERASE_SINGLE_COMPONENT,
CLIENT_ERASE_MANY_COMPONENTS,
CLIENT_UPDATE_COMPONENT,

View file

@ -185,15 +185,15 @@ MainServer::MainServer(World& initWorld, const std::string& serverLocalID):
return idsToRemove.contains(p.first);
});
});
netServer->add_recv_callback(SERVER_TRANSFORM_COMPONENT, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
ServerClientID idToTransform;
message(idToTransform);
auto it = data.idToComponentMap.find(idToTransform);
if(it != data.idToComponentMap.end()) {
std::shared_ptr<DrawComponent>& comp = it->second;
message(comp->coords);
comp->server_send_transform(*this, idToTransform);
netServer->add_recv_callback(SERVER_TRANSFORM_MANY_COMPONENTS, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
std::vector<std::pair<ServerClientID, CoordSpaceHelper>> transformsToSend;
message(transformsToSend);
for(auto& t : transformsToSend) {
auto it = data.idToComponentMap.find(t.first);
if(it != data.idToComponentMap.end())
it->second->coords = t.second;
}
DrawComponent::server_send_transform_many(*this, transformsToSend);
});
netServer->add_recv_callback(SERVER_UPDATE_COMPONENT, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
bool isTemp;