Improved online performance for canvases with many components

This commit is contained in:
Yousef Khadadeh 2025-07-26 21:20:14 +03:00
parent 8a36bc4b6d
commit b6a25280e6
16 changed files with 406 additions and 183 deletions

View file

@ -23,6 +23,7 @@
#include "../MainProgram.hpp"
#include <Helpers/Logger.hpp>
#include <Helpers/Parallel.hpp>
#include <cereal/types/unordered_set.hpp>
DrawingProgram::DrawingProgram(World& initWorld):
world(initWorld),
@ -52,15 +53,9 @@ DrawingProgram::DrawingProgram(World& initWorld):
};
components.clientEraseCallback = [&](const CollabListType::ObjectInfoPtr& c) {
compCache.erase_component(c);
delayedUpdateTransformComponents.erase(c->obj);
delayedUpdateComponents.erase(c->obj);
updateableComponents.erase(c->obj);
};
components.clientEraseSetCallback = [&](const std::unordered_set<CollabListType::ObjectInfoPtr>& comps) {
for(auto& c : comps) {
delayedUpdateTransformComponents.erase(c->obj);
updateableComponents.erase(c->obj);
}
};
components.clientInsertOrderedVectorCallback = [&](const std::vector<CollabListType::ObjectInfoPtr>& comps) {
for(auto& c : comps)
components.clientInsertCallback(c);
@ -75,10 +70,10 @@ void DrawingProgram::init_client_callbacks() {
bool isTemp;
ServerClientID id;
message(isTemp, id);
std::shared_ptr<DrawComponent>* compPtr = components.get_item_by_id(id);
if(!compPtr)
auto objPtr = components.get_item_by_id(id);
if(!objPtr)
return;
std::shared_ptr<DrawComponent>& comp = *compPtr;
std::shared_ptr<DrawComponent>& comp = objPtr->obj;
float dur = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - comp->lastUpdateTime).count();
if(dur > CLIENT_DRAWCOMP_DELAY_TIMER_DURATION){
comp->delayedUpdatePtr = nullptr;
@ -91,31 +86,22 @@ void DrawingProgram::init_client_callbacks() {
else {
comp->delayedUpdatePtr = DrawComponent::allocate_comp_type(comp->get_type());
message(*comp->delayedUpdatePtr);
delayedUpdateTransformComponents.emplace(comp);
delayedUpdateComponents.emplace(comp);
}
});
world.con.client_add_recv_callback(CLIENT_TRANSFORM_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
bool isTemp;
ServerClientID id;
message(isTemp, id);
std::shared_ptr<DrawComponent>* compPtr = components.get_item_by_id(id);
if(!compPtr)
message(id);
auto objPtr = components.get_item_by_id(id);
if(!objPtr)
return;
std::shared_ptr<DrawComponent>& comp = *compPtr;
float dur = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - comp->lastTransformTime).count();
if(dur > CLIENT_DRAWCOMP_DELAY_TIMER_DURATION){
comp->delayedCoordinateSpace = nullptr;
message(comp->coords);
if(!isTemp)
comp->finalize_update(*this); // No need to update draw data during transformation
}
else {
comp->delayedCoordinateSpace = std::make_shared<CoordSpaceHelper>();
message(*comp->delayedCoordinateSpace);
delayedUpdateTransformComponents.emplace(comp);
}
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_PLACE_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
world.con.client_add_recv_callback(CLIENT_PLACE_SINGLE_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
uint64_t insertPosition;
DrawComponentType type;
message(insertPosition, type);
@ -126,17 +112,42 @@ void DrawingProgram::init_client_callbacks() {
if(didntExistPreviously)
newObj->final_update(*this);
});
world.con.client_add_recv_callback(CLIENT_ERASE_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
ServerClientID cToRemove;
message(cToRemove);
components.server_erase(cToRemove);
world.con.client_add_recv_callback(CLIENT_PLACE_MANY_COMPONENTS, [&](cereal::PortableBinaryInputArchive& message) {
std::vector<CollabListType::ObjectInfoPtr> sortedPlacedComponents;
SendOrderedComponentVectorOp a{&sortedPlacedComponents};
message(a);
std::vector<bool> didntExistPreviously = components.server_insert_ordered_vector(sortedPlacedComponents);
if(sortedPlacedComponents.size() >= DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD) {
parallel_loop_container(sortedPlacedComponents, [&](const auto& c){
c->obj->final_update(*this, false);
});
force_rebuild_cache();
}
else {
for(size_t i = 0; i < sortedPlacedComponents.size(); i++) {
if(didntExistPreviously[i])
sortedPlacedComponents[i]->obj->final_update(*this);
}
}
});
world.con.client_add_recv_callback(CLIENT_ERASE_SINGLE_COMPONENT, [&](cereal::PortableBinaryInputArchive& message) {
ServerClientID idToErase;
message(idToErase);
components.server_erase(idToErase);
});
world.con.client_add_recv_callback(CLIENT_ERASE_MANY_COMPONENTS, [&](cereal::PortableBinaryInputArchive& message) {
std::unordered_set<ServerClientID> idsToErase;
message(idsToErase);
components.server_erase_set(idsToErase);
});
}
void DrawingProgram::check_delayed_update_transform_timers() {
std::erase_if(delayedUpdateTransformComponents, [&](auto& comp) {
void DrawingProgram::check_delayed_update_timers() {
std::erase_if(delayedUpdateComponents, [&](auto& comp) {
comp->check_timers(*this);
return !comp->delayedUpdatePtr && !comp->delayedCoordinateSpace;
return !comp->delayedUpdatePtr;
});
}
@ -352,11 +363,26 @@ void DrawingProgram::update() {
if(controls.leftClickReleased)
controls.leftClickReleased = false;
check_delayed_update_transform_timers();
check_delayed_update_timers();
check_updateable_components();
compCache.update();
selection.update();
if(controls.selectedTool == TOOL_ERASER)
compCache.test_rebuild_dont_include_set_dont_include_nodes(components.client_list(), eraserTool.erasedComponents, eraserTool.erasedBVHNodes);
else if(controls.selectedTool == TOOL_RECTSELECT)
compCache.test_rebuild_dont_include_set(components.client_list(), selection.get_selected_set());
else
compCache.test_rebuild(components.client_list());
}
void DrawingProgram::force_rebuild_cache() {
if(controls.selectedTool == TOOL_ERASER)
compCache.test_rebuild_dont_include_set_dont_include_nodes(components.client_list(), eraserTool.erasedComponents, eraserTool.erasedBVHNodes, true);
else if(controls.selectedTool == TOOL_RECTSELECT)
compCache.test_rebuild_dont_include_set(components.client_list(), selection.get_selected_set(), true);
else
compCache.test_rebuild(components.client_list(), true);
}
void DrawingProgram::drag_drop_update() {
@ -448,7 +474,7 @@ void DrawingProgram::initialize_draw_data(cereal::PortableBinaryInputArchive& a)
parallel_loop_all_components([&](const auto& c){
c->obj->final_update(*this, false);
});
compCache.force_rebuild(components.client_list());
compCache.test_rebuild(components.client_list(), true);
}
void DrawingProgram::add_undo_place_component(uint64_t placement, const std::shared_ptr<DrawComponent>& comp) {
@ -476,15 +502,17 @@ void DrawingProgram::add_undo_place_components(uint64_t placement, const std::ve
world.undo.push(UndoManager::UndoRedoPair{
[&, comps]() {
bool toRet = true;
std::unordered_set<ServerClientID> idsToErase;
for(auto& c : comps) {
if(!c->collabListInfo.lock())
toRet = false;
else {
idsToErase.emplace(c->collabListInfo.lock()->id);
ServerClientID compID;
c->client_send_erase(*this);
components.client_erase(c, compID);
}
}
DrawComponent::client_send_erase_set(*this, idsToErase);
reset_tools();
return toRet;
},

View file

@ -55,14 +55,16 @@ class DrawingProgram {
CollabListType components;
std::unordered_set<std::shared_ptr<DrawComponent>> updateableComponents;
std::unordered_set<std::shared_ptr<DrawComponent>> delayedUpdateTransformComponents;
std::unordered_set<std::shared_ptr<DrawComponent>> delayedUpdateComponents;
Vector4f* get_foreground_color_ptr();
private:
void check_delayed_update_transform_timers();
void check_delayed_update_timers();
void check_updateable_components();
void force_rebuild_cache();
std::atomic<bool> addFileInNextFrame = false;
std::pair<std::string, Vector2f> addFileInfo;

View file

@ -49,9 +49,45 @@ void DrawingProgramCache::clear_own_cached_surfaces() {
});
}
void DrawingProgramCache::update() {
if(!disableRefresh && unsortedComponents.size() >= 1000 && (std::chrono::steady_clock::now() - lastBvhBuildTime) >= std::chrono::seconds(5))
force_rebuild(drawP.components.client_list());
bool DrawingProgramCache::check_if_rebuild_should_occur() {
return (unsortedComponents.size() >= MINIMUM_COMPONENTS_TO_START_REBUILD && (std::chrono::steady_clock::now() - lastBvhBuildTime) >= std::chrono::seconds(5));
}
void DrawingProgramCache::test_rebuild(const std::vector<CollabListType::ObjectInfoPtr>& comps, bool force) {
if(force || check_if_rebuild_should_occur())
force_rebuild(comps);
}
void DrawingProgramCache::test_rebuild_dont_include_set(const std::vector<CollabListType::ObjectInfoPtr>& comps, const std::unordered_set<CollabListType::ObjectInfoPtr>& objsToNotInclude, bool force) {
if(force || check_if_rebuild_should_occur())
force_rebuild_dont_include_objs(comps, objsToNotInclude);
}
void DrawingProgramCache::test_rebuild_dont_include_set_dont_include_nodes(const std::vector<CollabListType::ObjectInfoPtr>& comps, const std::unordered_set<CollabListType::ObjectInfoPtr>& objsToNotInclude, const std::unordered_set<std::shared_ptr<DrawingProgramCacheBVHNode>>& nodesToNotInclude, bool force) {
if(force || check_if_rebuild_should_occur()) {
auto objsToNotIncludeNew = objsToNotInclude;
move_components_from_bvh_nodes_to_set(objsToNotIncludeNew, nodesToNotInclude);
force_rebuild_dont_include_objs(comps, objsToNotIncludeNew);
}
}
void DrawingProgramCache::move_components_from_bvh_nodes_to_set(std::unordered_set<CollabListType::ObjectInfoPtr>& s, const std::unordered_set<std::shared_ptr<DrawingProgramCacheBVHNode>>& bvhNodes) {
for(auto& b : bvhNodes)
move_components_from_bvh_node_to_set(s, b);
}
void DrawingProgramCache::move_components_from_bvh_node_to_set(std::unordered_set<CollabListType::ObjectInfoPtr>& s, const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode) {
for(auto& c : bvhNode->components)
s.emplace(c);
for(auto& p : bvhNode->children)
move_components_from_bvh_node_to_set(s, p);
}
void DrawingProgramCache::force_rebuild_dont_include_objs(std::vector<CollabListType::ObjectInfoPtr> componentsToBuild, const std::unordered_set<CollabListType::ObjectInfoPtr>& objsToNotInclude) {
std::erase_if(componentsToBuild, [&objsToNotInclude](auto& c) {
return objsToNotInclude.contains(c);
});
build(componentsToBuild);
}
void DrawingProgramCache::force_rebuild(const std::vector<CollabListType::ObjectInfoPtr>& componentsToBuild) {
@ -151,7 +187,7 @@ void DrawingProgramCache::build_bvh_node(const std::shared_ptr<DrawingProgramCac
build_bvh_node_coords_and_resolution(*bvhNode);
if(components.size() < 100) {
if(components.size() < MAXIMUM_COMPONENTS_IN_SINGLE_NODE) {
bvhNode->components = components;
for(auto& c : bvhNode->components)
c->obj->parentBvhNode = bvhNode;

View file

@ -33,16 +33,24 @@ class DrawingProgramCache {
DrawingProgramCache(DrawingProgram& initDrawP);
~DrawingProgramCache();
static std::unordered_set<std::shared_ptr<DrawingProgramCacheBVHNode>> nodesWithCachedSurfaces;
bool disableRefresh = false;
static constexpr size_t MINIMUM_COMPONENTS_TO_START_REBUILD = 1000;
static constexpr size_t MAXIMUM_COMPONENTS_IN_SINGLE_NODE = 100;
static std::unordered_set<std::shared_ptr<DrawingProgramCacheBVHNode>> nodesWithCachedSurfaces;
bool check_if_rebuild_should_occur();
void test_rebuild(const std::vector<CollabListType::ObjectInfoPtr>& comps, bool force = false);
void test_rebuild_dont_include_set(const std::vector<CollabListType::ObjectInfoPtr>& comps, const std::unordered_set<CollabListType::ObjectInfoPtr>& objsToNotInclude, bool force = false);
void test_rebuild_dont_include_set_dont_include_nodes(const std::vector<CollabListType::ObjectInfoPtr>& comps, const std::unordered_set<CollabListType::ObjectInfoPtr>& objsToNotInclude, const std::unordered_set<std::shared_ptr<DrawingProgramCacheBVHNode>>& nodesToNotInclude, bool force = false);
static void move_components_from_bvh_nodes_to_set(std::unordered_set<CollabListType::ObjectInfoPtr>& s, const std::unordered_set<std::shared_ptr<DrawingProgramCacheBVHNode>>& bvhNodes);
static void move_components_from_bvh_node_to_set(std::unordered_set<CollabListType::ObjectInfoPtr>& s, const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode);
void update();
void erase_component(const CollabListType::ObjectInfoPtr& c);
void add_component(const CollabListType::ObjectInfoPtr& c);
void refresh_all_draw_cache(const DrawData& drawData);
void traverse_bvh_run_function(const SCollision::AABB<WorldScalar>& aabb, std::function<bool(const std::shared_ptr<DrawingProgramCacheBVHNode>& node, const std::vector<CollabListType::ObjectInfoPtr>& components)> f);
void traverse_bvh_erase_function(const SCollision::AABB<WorldScalar>& aabb, std::function<bool(const std::shared_ptr<DrawingProgramCacheBVHNode>& node, std::vector<CollabListType::ObjectInfoPtr>& components)> f);
void force_rebuild(const std::vector<CollabListType::ObjectInfoPtr>& componentsToBuild);
// Run before actually updating the component
void preupdate_component(const CollabListType::ObjectInfoPtr& c);
void invalidate_cache_before_pos(uint64_t placementToInvalidateAt);
@ -53,6 +61,9 @@ class DrawingProgramCache {
const std::chrono::steady_clock::time_point& get_last_bvh_build_time() const;
void clear_own_cached_surfaces();
private:
void force_rebuild_dont_include_objs(std::vector<CollabListType::ObjectInfoPtr> componentsToBuild, const std::unordered_set<CollabListType::ObjectInfoPtr>& objsToNotInclude);
void force_rebuild(const std::vector<CollabListType::ObjectInfoPtr>& componentsToBuild);
bool traverse_bvh_erase_function_recursive(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode, const SCollision::AABB<WorldScalar>& aabb, std::function<bool(const std::shared_ptr<DrawingProgramCacheBVHNode>& node, std::vector<CollabListType::ObjectInfoPtr>& components)> f);
void build_bvh_node_coords_and_resolution(DrawingProgramCacheBVHNode& node);
void refresh_draw_cache(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode, const DrawData& drawData);

View file

@ -14,6 +14,10 @@ DrawingProgramSelection::DrawingProgramSelection(DrawingProgram& initDrawP):
drawP(initDrawP)
{}
const std::unordered_set<CollabListType::ObjectInfoPtr>& DrawingProgramSelection::get_selected_set() {
return selectedSet;
}
void DrawingProgramSelection::add_from_cam_coord_collider_to_selection(const SCollision::ColliderCollection<float>& cC) {
auto cCWorld = drawP.world.drawData.cam.c.collider_to_world<SCollision::ColliderCollection<WorldScalar>, SCollision::ColliderCollection<float>>(cC);
@ -73,19 +77,26 @@ void DrawingProgramSelection::deselect_all() {
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);
});
selectedSet.clear();
cache.clear();
selectionTransformCoords = CoordSpaceHelper();
drawP.compCache.force_rebuild(drawP.components.client_list());
drawP.force_rebuild_cache();
transformOpHappening = TransformOperation::NONE;
}
}
bool DrawingProgramSelection::is_selected(const CollabListType::ObjectInfoPtr& objToCheck) {
return selectedSet.contains(objToCheck);
}
void DrawingProgramSelection::update() {
if(is_something_selected()) {
if(cache.get_unsorted_component_list().size() >= 1000 && (std::chrono::steady_clock::now() - cache.get_last_bvh_build_time()) >= std::chrono::seconds(5))
cache.force_rebuild(std::vector<CollabListType::ObjectInfoPtr>(selectedSet.begin(), selectedSet.end()));
if(cache.check_if_rebuild_should_occur())
cache.test_rebuild(std::vector<CollabListType::ObjectInfoPtr>(selectedSet.begin(), selectedSet.end()), true);
if(drawP.world.main.input.key(InputManager::KEY_DRAW_UNSELECT).pressed)
deselect_all();

View file

@ -13,6 +13,8 @@ class DrawingProgramSelection {
void draw_gui(SkCanvas* canvas, const DrawData& drawData);
bool is_something_selected();
bool mouse_collided_with_selection();
bool is_selected(const CollabListType::ObjectInfoPtr& objToCheck);
const std::unordered_set<CollabListType::ObjectInfoPtr>& get_selected_set();
private:
bool mouse_collided_with_selection_aabb();
bool mouse_collided_with_scale_point();

View file

@ -22,16 +22,17 @@ void EraserTool::gui_toolbox() {
void EraserTool::reset_tool() {
if(!erasedComponents.empty()) {
for(auto& bvhNode : erasedBVHNodes)
move_erased_components_from_bvh_nodes_recursive(bvhNode);
DrawingProgramCache::move_components_from_bvh_nodes_to_set(erasedComponents, erasedBVHNodes);
std::unordered_set<ServerClientID> idsToErase;
for(auto& c : erasedComponents)
c->obj->client_send_erase(drawP);
idsToErase.emplace(c->id);
DrawComponent::client_send_erase_set(drawP, idsToErase);
drawP.components.client_erase_set(erasedComponents);
drawP.world.undo.push(UndoManager::UndoRedoPair{
[&, erasedCompVal = erasedComponents]() {
std::vector<CollabListType::ObjectInfoPtr> sortedObjects(erasedCompVal.begin(), erasedCompVal.end());
[&, erasedComponents = erasedComponents]() {
std::vector<CollabListType::ObjectInfoPtr> sortedObjects(erasedComponents.begin(), erasedComponents.end());
std::sort(sortedObjects.begin(), sortedObjects.end(), [](auto& a, auto& b) {
return a->pos < b->pos;
});
@ -41,27 +42,27 @@ void EraserTool::reset_tool() {
return false;
drawP.components.client_insert_ordered_vector(sortedObjects);
for(auto& comp : sortedObjects)
comp->obj->client_send_place(drawP);
DrawComponent::client_send_place_many(drawP, sortedObjects);
drawP.reset_tools();
return true;
},
[&, erasedCompVal = erasedComponents]() {
for(auto& comp : erasedCompVal)
[&, erasedComponents = erasedComponents]() {
for(auto& comp : erasedComponents)
if(!comp->obj->collabListInfo.lock())
return false;
for(auto& comp : erasedCompVal)
comp->obj->client_send_erase(drawP);
std::unordered_set<ServerClientID> idsToErase;
for(auto& c : erasedComponents)
idsToErase.emplace(c->id);
drawP.components.client_erase_set(erasedCompVal);
DrawComponent::client_send_erase_set(drawP, idsToErase);
drawP.components.client_erase_set(erasedComponents);
drawP.reset_tools();
drawP.compCache.force_rebuild(drawP.components.client_list());
drawP.force_rebuild_cache();
return true;
}
@ -69,7 +70,6 @@ void EraserTool::reset_tool() {
}
erasedComponents.clear();
erasedBVHNodes.clear();
drawP.compCache.disableRefresh = false;
}
void EraserTool::tool_update() {
@ -82,8 +82,6 @@ void EraserTool::tool_update() {
SCollision::generate_wide_line(cC, prevMousePos, drawP.world.main.input.mouse.pos, drawP.controls.relativeWidth * 2.0f, true);
auto cCWorld = drawP.world.drawData.cam.c.collider_to_world<SCollision::ColliderCollection<WorldScalar>, SCollision::ColliderCollection<float>>(cC);
drawP.compCache.disableRefresh = true;
drawP.compCache.traverse_bvh_erase_function(cCWorld.bounds, [&](const auto& bvhNode, auto& comps) {
if(bvhNode &&
SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.min)) &&
@ -109,13 +107,6 @@ void EraserTool::tool_update() {
reset_tool();
}
void EraserTool::move_erased_components_from_bvh_nodes_recursive(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode) {
for(auto& c : bvhNode->components)
erasedComponents.emplace(c);
for(auto& p : bvhNode->children)
move_erased_components_from_bvh_nodes_recursive(p);
}
void EraserTool::draw(SkCanvas* canvas, const DrawData& drawData) {
if(!drawData.main->toolbar.io->hoverObstructed) {
SkColor4f c = drawP.world.main.canvasTheme.toolFrontColor;

View file

@ -12,11 +12,10 @@ class EraserTool {
void tool_update();
void reset_tool();
void draw(SkCanvas* canvas, const DrawData& drawData);
private:
std::unordered_set<CollabListType::ObjectInfoPtr> erasedComponents;
std::unordered_set<std::shared_ptr<DrawingProgramCacheBVHNode>> erasedBVHNodes;
void move_erased_components_from_bvh_nodes_recursive(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode);
private:
DrawingProgram& drawP;
};

View file

@ -35,13 +35,11 @@ void RectSelectTool::gui_toolbox() {
void RectSelectTool::reset_tool() {
drawP.selection.deselect_all();
controls = RectSelectControls();
drawP.compCache.disableRefresh = false;
}
void RectSelectTool::tool_update() {
switch(controls.selectionMode) {
case 0: {
drawP.compCache.disableRefresh = false;
if(drawP.controls.leftClick) {
if(drawP.selection.is_something_selected()) {
if(!drawP.selection.mouse_collided_with_selection())