diff --git a/src/CollabList.hpp b/src/CollabList.hpp index 0950899..dd910fb 100644 --- a/src/CollabList.hpp +++ b/src/CollabList.hpp @@ -23,6 +23,8 @@ template class CollabList { std::function updateCallback; std::function clientInsertCallback; std::function clientEraseCallback; + std::function& c)> clientEraseSetCallback; + std::function& c)> clientInsertOrderedVectorCallback; std::function clientServerLastPosShiftCallback; void set_server_from_client_list() { @@ -60,6 +62,7 @@ template class CollabList { if(func((uint64_t)i, clientSideList[i])) { if(clientEraseCallback) clientEraseCallback(clientSideList[i]); + clientSideList[i]->obj->collabListInfo.reset(); clientSideList.erase(clientSideList.begin() + i); i--; if(updateCallback) @@ -98,15 +101,69 @@ template class CollabList { clientEraseCallback(obj); erasedID = obj->id; obj->syncIgnore = true; - if(updateCallback) - updateCallback(); + obj->obj->collabListInfo.reset(); erasedSomething = true; return true; } return false; }); - if(erasedSomething) + if(erasedSomething) { + if(updateCallback) + updateCallback(); complete_client_pos_refresh(false); + } + } + + void client_erase_set(const std::unordered_set& objSet) { + bool erasedSomething = false; + std::erase_if(clientSideList, [&](const auto& obj) { + if(objSet.contains(obj)) { + obj->syncIgnore = true; + obj->obj->collabListInfo.reset(); + erasedSomething = true; + return true; + } + return false; + }); + if(erasedSomething) { + complete_client_pos_refresh(false); + if(updateCallback) + updateCallback(); + if(clientEraseSetCallback) + clientEraseSetCallback(objSet); + for(auto& o : objSet) + o->obj->collabListInfo.reset(); + } + } + + // NOTE: Dont use this function unless you're certain every object in the vector isn't already in the client list + void client_insert_ordered_vector(const std::vector& objOrderedVector) { + if(objOrderedVector.empty()) + return; + + uint64_t startPoint = 0; + objOrderedVector.front()->syncIgnore = true; + objOrderedVector.front()->id = getNewIDFunc(); + objOrderedVector.front()->obj->collabListInfo = objOrderedVector.front(); + + for(uint64_t i = 1; i < static_cast(objOrderedVector.size()); i++) { + auto& obj = objOrderedVector[i]; + obj->syncIgnore = true; + obj->id = getNewIDFunc(); + obj->obj->collabListInfo = obj; + if(obj->pos - objOrderedVector[startPoint]->pos != (i - startPoint)) { + uint64_t insertPosition = std::max(clientSideList.size(), objOrderedVector[startPoint]->pos); + clientSideList.insert(clientSideList.begin() + insertPosition, objOrderedVector.begin() + startPoint, objOrderedVector.begin() + i); + startPoint = i; + } + } + clientSideList.insert(clientSideList.begin() + objOrderedVector[startPoint]->pos, objOrderedVector.begin() + startPoint, objOrderedVector.end()); + + complete_client_pos_refresh(false); + if(updateCallback) + updateCallback(); + if(clientInsertOrderedVectorCallback) + clientInsertOrderedVectorCallback(objOrderedVector); } bool server_insert(uint64_t pos, IDType id, const T& item) { @@ -152,6 +209,7 @@ template class CollabList { obj->syncIgnore = false; if(clientEraseCallback) clientEraseCallback(obj); + obj->obj->collabListInfo.reset(); return true; } return false; diff --git a/src/DrawingProgram/DrawingProgram.cpp b/src/DrawingProgram/DrawingProgram.cpp index aa6384f..854979b 100644 --- a/src/DrawingProgram/DrawingProgram.cpp +++ b/src/DrawingProgram/DrawingProgram.cpp @@ -66,6 +66,16 @@ DrawingProgram::DrawingProgram(World& initWorld): delayedUpdateTransformComponents.erase(c->obj); updateableComponents.erase(c->obj); }; + components.clientEraseSetCallback = [&](const std::unordered_set& comps) { + for(auto& c : comps) { + delayedUpdateTransformComponents.erase(c->obj); + updateableComponents.erase(c->obj); + } + }; + components.clientInsertOrderedVectorCallback = [&](const std::vector& comps) { + for(auto& c : comps) + components.clientInsertCallback(c); + }; components.clientServerLastPosShiftCallback = [&](uint64_t lastShiftPos) { compCache.invalidate_cache_before_pos(lastShiftPos); }; @@ -445,6 +455,7 @@ void DrawingProgram::reset_tools() { rectDrawTool.reset_tool(); ellipseDrawTool.reset_tool(); editTool.reset_tool(); + eraserTool.reset_tool(); reset_selection(); } @@ -611,7 +622,7 @@ void DrawingProgram::draw(SkCanvas* canvas, const DrawData& drawData) { } } -uint64_t DrawingProgram::draw_components_to_canvas(SkCanvas* canvas, const DrawData& drawData, bool dontUseCache) { +void DrawingProgram::draw_components_to_canvas(SkCanvas* canvas, const DrawData& drawData, bool dontUseCache, uint64_t* lastDrawnComponentPlacement) { uint64_t lastComponentDrawn = 0; std::vector> cachedNodesToDraw; @@ -681,7 +692,8 @@ uint64_t DrawingProgram::draw_components_to_canvas(SkCanvas* canvas, const DrawD canvas->restore(); } - return lastComponentDrawn; + if(lastDrawnComponentPlacement) + *lastDrawnComponentPlacement = lastComponentDrawn; } Vector4f* DrawingProgram::get_foreground_color_ptr() { diff --git a/src/DrawingProgram/DrawingProgram.hpp b/src/DrawingProgram/DrawingProgram.hpp index 7b6b630..123f4b7 100644 --- a/src/DrawingProgram/DrawingProgram.hpp +++ b/src/DrawingProgram/DrawingProgram.hpp @@ -86,7 +86,7 @@ class DrawingProgram { void add_undo_place_component(uint64_t placement, const std::shared_ptr& comp); void add_undo_place_components(uint64_t placement, const std::vector>& comps); - uint64_t draw_components_to_canvas(SkCanvas* canvas, const DrawData& drawData, bool dontUseCache); + void draw_components_to_canvas(SkCanvas* canvas, const DrawData& drawData, bool dontUseCache, uint64_t* lastDrawnComponentPlacement = nullptr); BrushTool brushTool; EraserTool eraserTool; diff --git a/src/DrawingProgram/DrawingProgramCache.cpp b/src/DrawingProgram/DrawingProgramCache.cpp index ddc8a67..65ffa88 100644 --- a/src/DrawingProgram/DrawingProgramCache.cpp +++ b/src/DrawingProgram/DrawingProgramCache.cpp @@ -11,7 +11,7 @@ #include #endif -#define MAXIMUM_DRAW_CACHE_SURFACES 64 +#define MAXIMUM_DRAW_CACHE_SURFACES 40 std::unordered_set> DrawingProgramCache::nodesWithCachedSurfaces; @@ -44,7 +44,7 @@ void DrawingProgramCache::clear_own_cached_surfaces() { } void DrawingProgramCache::update() { - if(unsortedComponents.size() >= 1000 && (std::chrono::steady_clock::now() - lastBvhBuildTime) >= std::chrono::seconds(5)) + if(!disableRefresh && unsortedComponents.size() >= 1000 && (std::chrono::steady_clock::now() - lastBvhBuildTime) >= std::chrono::seconds(5)) force_rebuild(); } @@ -54,7 +54,10 @@ void DrawingProgramCache::force_rebuild() { void DrawingProgramCache::add_component(const CollabListType::ObjectInfoPtr& c) { unsortedComponents.emplace_back(c); - invalidate_cache_before_pos(c->pos); + if(c->obj->worldAABB) + invalidate_cache_at_aabb_before_pos(c->obj->worldAABB.value(), c->pos); + else + invalidate_cache_before_pos(c->pos); } void DrawingProgramCache::erase_component(const CollabListType::ObjectInfoPtr& c) { @@ -85,6 +88,23 @@ std::pair, std::vectorcomponents.end()}; } +void DrawingProgramCache::traverse_bvh_erase_function(const SCollision::AABB& aabb, std::function& node, std::vector& components)> f) { + f(nullptr, unsortedComponents); + if(traverse_bvh_erase_function_recursive(bvhRoot, aabb, f)) + bvhRoot = nullptr; +} + +bool DrawingProgramCache::traverse_bvh_erase_function_recursive(const std::shared_ptr& bvhNode, const SCollision::AABB& aabb, std::function& node, std::vector& components)> f) { + if(bvhNode && SCollision::collide(aabb, bvhNode->bounds)) { + if(f(bvhNode, bvhNode->components)) + return true; + std::erase_if(bvhNode->children, [&](auto& p) { + return traverse_bvh_erase_function_recursive(p, aabb, f); + }); + } + return false; +} + void DrawingProgramCache::traverse_bvh_collision_check(const SCollision::ColliderCollection& checkAgainstWorld, const SCollision::ColliderCollection& checkAgainstCam) { for(auto& c : drawP.components.client_list()) c->obj->globalCollisionCheck = false; @@ -120,7 +140,7 @@ void DrawingProgramCache::traverse_bvh_collision_check_recursive(const std::shar } } -void DrawingProgramCache::traverse_bvh_run_function(const SCollision::AABB& aabb, std::function node, const std::vector& components)> f) { +void DrawingProgramCache::traverse_bvh_run_function(const SCollision::AABB& aabb, std::function& node, const std::vector& components)> f) { f(nullptr, unsortedComponents); traverse_bvh_run_function_recursive(bvhRoot, aabb, f); } @@ -225,6 +245,16 @@ void DrawingProgramCache::invalidate_cache_before_pos(uint64_t placementToInvali }); } +void DrawingProgramCache::invalidate_cache_at_aabb_before_pos(const SCollision::AABB& aabb, uint64_t placementToInvalidateAt) { + std::erase_if(nodesWithCachedSurfaces, [&](auto& n) { + if(placementToInvalidateAt <= n->drawCache.value().lastDrawnComponentPlacement && SCollision::collide(aabb, n->bounds)) { + n->drawCache = std::nullopt; + return true; + } + return false; + }); +} + void DrawingProgramCache::refresh_all_draw_cache(SkCanvas* canvas, const DrawData& drawData) { std::deque> nodeFlatList; // We want to render children before parents, so that parents can make use of the cached children traverse_bvh_run_function(drawData.cam.viewingAreaGenerousCollider, [&](std::shared_ptr node, const std::vector& comps) { @@ -266,7 +296,7 @@ void DrawingProgramCache::refresh_draw_cache(const std::shared_ptr& bvh cacheDrawData.cam.set_viewing_area(bvhNode->resolution.cast()); cacheDrawData.refresh_draw_optimizing_values(); - drawCache.lastDrawnComponentPlacement = drawP.draw_components_to_canvas(cacheCanvas, cacheDrawData, false); + drawP.draw_components_to_canvas(cacheCanvas, cacheDrawData, false, &drawCache.lastDrawnComponentPlacement); drawCache.lastRenderTime = std::chrono::steady_clock::now(); drawCache.attachedDrawP = &drawP; diff --git a/src/DrawingProgram/DrawingProgramCache.hpp b/src/DrawingProgram/DrawingProgramCache.hpp index d92e6fd..c6458cd 100644 --- a/src/DrawingProgram/DrawingProgramCache.hpp +++ b/src/DrawingProgram/DrawingProgramCache.hpp @@ -28,6 +28,7 @@ class DrawingProgramCache { Vector2i resolution; struct DrawCacheData { uint64_t lastDrawnComponentPlacement; + uint64_t firstDrawnComponentPlacement; sk_sp surface; std::chrono::steady_clock::time_point lastRenderTime; DrawingProgram* attachedDrawP; @@ -38,16 +39,24 @@ class DrawingProgramCache { static std::unordered_set> nodesWithCachedSurfaces; + bool disableRefresh = false; + + void traverse_bvh_run_function(const SCollision::AABB& aabb, std::function& node, const std::vector& components)> f); void traverse_bvh_collision_check(const SCollision::ColliderCollection& checkAgainstWorld, const SCollision::ColliderCollection& checkAgainstCam); - void traverse_bvh_run_function(const SCollision::AABB& aabb, std::function node, const std::vector& components)> f); + + void traverse_bvh_erase_function(const SCollision::AABB& aabb, std::function& node, std::vector& components)> f); + void force_rebuild(); // Run before actually updating the component void preupdate_component(const CollabListType::ObjectInfoPtr& c); void invalidate_cache_before_pos(uint64_t placementToInvalidateAt); + void invalidate_cache_at_aabb_before_pos(const SCollision::AABB& aabb, uint64_t placementToInvalidateAt); private: void clear_own_cached_surfaces(); + bool traverse_bvh_erase_function_recursive(const std::shared_ptr& bvhNode, const SCollision::AABB& aabb, std::function& node, std::vector& components)> f); + void traverse_bvh_collision_check_recursive(const std::shared_ptr& bvhNode, const SCollision::ColliderCollection& checkAgainstWorld, const SCollision::ColliderCollection& checkAgainstCam); void traverse_bvh_collision_check_recursive_tiny(const std::shared_ptr& bvhNode); @@ -62,7 +71,11 @@ class DrawingProgramCache { std::pair, std::vector::iterator> get_bvh_node_fully_containing_recursive(const std::shared_ptr& bvhNode, DrawComponent* c); void build_bvh_node(const std::shared_ptr& bvhNode, std::vector components); + std::shared_ptr bvhRoot; std::vector unsortedComponents; + + std::unordered_set componentsSetToErase; + DrawingProgram& drawP; }; diff --git a/src/DrawingProgram/EraserTool.cpp b/src/DrawingProgram/EraserTool.cpp index b18e8d9..bab2f9e 100644 --- a/src/DrawingProgram/EraserTool.cpp +++ b/src/DrawingProgram/EraserTool.cpp @@ -20,6 +20,58 @@ void EraserTool::gui_toolbox() { t.gui.pop_id(); } +void EraserTool::reset_tool() { + if(!erasedComponents.empty()) { + for(auto& bvhNode : erasedBVHNodes) + move_erased_components_from_bvh_nodes_recursive(bvhNode); + + for(auto& c : erasedComponents) + c->obj->client_send_erase(drawP); + drawP.components.client_erase_set(erasedComponents); + + drawP.world.undo.push(UndoManager::UndoRedoPair{ + [&, erasedCompVal = erasedComponents]() { + std::vector sortedObjects(erasedCompVal.begin(), erasedCompVal.end()); + std::sort(sortedObjects.begin(), sortedObjects.end(), [](auto& a, auto& b) { + return a->pos < b->pos; + }); + + for(auto& comp : sortedObjects) + if(comp->obj->collabListInfo.lock()) + return false; + + drawP.components.client_insert_ordered_vector(sortedObjects); + + for(auto& comp : sortedObjects) + comp->obj->client_send_place(drawP); + + drawP.reset_tools(); + + return true; + }, + [&, erasedCompVal = erasedComponents]() { + for(auto& comp : erasedCompVal) + if(!comp->obj->collabListInfo.lock()) + return false; + + for(auto& comp : erasedCompVal) + comp->obj->client_send_erase(drawP); + + drawP.components.client_erase_set(erasedCompVal); + + drawP.reset_tools(); + + drawP.compCache.force_rebuild(); + + return true; + } + }); + } + erasedComponents.clear(); + erasedBVHNodes.clear(); + drawP.compCache.disableRefresh = false; +} + void EraserTool::tool_update() { if(drawP.controls.cursorHoveringOverCanvas) drawP.world.main.input.hideCursor = true; @@ -28,47 +80,43 @@ void EraserTool::tool_update() { const Vector2f& prevMousePos = drawP.controls.leftClick ? drawP.world.main.input.mouse.pos : drawP.world.main.input.mouse.lastPos; SCollision::ColliderCollection cC; SCollision::generate_wide_line(cC, prevMousePos, drawP.world.main.input.mouse.pos, drawP.controls.relativeWidth * 2.0f, true); - drawP.check_all_collisions_transform(cC); - drawP.components.client_erase_if([&](uint64_t oldPlacement, const auto& c) { - if(c->obj->globalCollisionCheck) { - erasedComponents.emplace_back(oldPlacement, c->obj); - c->obj->client_send_erase(drawP); + auto cCWorld = drawP.world.drawData.cam.c.collider_to_world, SCollision::ColliderCollection>(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)) && + SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.max)) && + SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.top_right())) && + SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.bottom_left()))) { + erasedBVHNodes.emplace(bvhNode); + drawP.compCache.invalidate_cache_at_aabb_before_pos(bvhNode->bounds, 0); return true; } + std::erase_if(comps, [&](auto& c) { + if(c->obj->collides_with(drawP.world.drawData.cam.c, cCWorld, cC, drawP.colliderAllocated)) { + erasedComponents.emplace(c); + if(c->obj->worldAABB) + drawP.compCache.invalidate_cache_at_aabb_before_pos(c->obj->worldAABB.value(), c->obj->collabListInfo.lock()->pos); + else + drawP.compCache.invalidate_cache_before_pos(c->obj->collabListInfo.lock()->pos); + return true; + } + return false; + }); return false; }); } - else if(!erasedComponents.empty()) { - auto erasedCompVal = erasedComponents; - drawP.world.undo.push(UndoManager::UndoRedoPair{ - [&, erasedCompVal]() { - for(auto& comp : erasedCompVal) - if(drawP.components.get_id(comp.second) != ServerClientID{0, 0}) - return false; + else + reset_tool(); +} - for(auto& comp : erasedCompVal | std::views::reverse) { - drawP.components.client_insert(comp.first, comp.second); - comp.second->client_send_place(drawP); - } - drawP.reset_tools(); - return true; - }, - [&, erasedCompVal]() { - for(auto& comp : erasedCompVal) - if(drawP.components.get_id(comp.second) == ServerClientID{0, 0}) - return false; - - for(auto& comp : erasedCompVal) { - ServerClientID compID; - comp.second->client_send_erase(drawP); - drawP.components.client_erase(comp.second, compID); - } - drawP.reset_tools(); - return true; - } - }); - erasedComponents.clear(); - } +void EraserTool::move_erased_components_from_bvh_nodes_recursive(const std::shared_ptr& 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) { diff --git a/src/DrawingProgram/EraserTool.hpp b/src/DrawingProgram/EraserTool.hpp index 23643de..d5f804d 100644 --- a/src/DrawingProgram/EraserTool.hpp +++ b/src/DrawingProgram/EraserTool.hpp @@ -10,10 +10,13 @@ class EraserTool { EraserTool(DrawingProgram& initDrawP); void gui_toolbox(); void tool_update(); + void reset_tool(); void draw(SkCanvas* canvas, const DrawData& drawData); private: + std::unordered_set, ServerClientID>::ObjectInfoPtr> erasedComponents; + std::unordered_set> erasedBVHNodes; - std::vector>> erasedComponents; // + void move_erased_components_from_bvh_nodes_recursive(const std::shared_ptr& bvhNode); DrawingProgram& drawP; };