Fixed integer division by zero error when loading files

This commit is contained in:
Yousef Khadadeh 2026-04-03 14:18:04 +03:00
parent 58fb02f449
commit 97d308268d
19 changed files with 97 additions and 30 deletions

View file

@ -102,16 +102,14 @@ void CanvasComponentContainer::draw_with_predraw_data(SkCanvas* canvas, const Dr
}
void CanvasComponentContainer::commit_update(DrawingProgram& drawP) {
if(worldAABB.has_value()) // This is the only point where worldAABB could be nullopt
drawP.preupdate_component(&(*objInfo));
drawP.preupdate_component(&(*objInfo));
get_comp().initialize_draw_data(drawP);
calculate_world_bounds();
drawP.preupdate_component(&(*objInfo));
}
void CanvasComponentContainer::commit_transform(DrawingProgram& drawP) {
if(worldAABB.has_value()) // This is the only point where worldAABB could be nullopt
drawP.preupdate_component(&(*objInfo));
drawP.preupdate_component(&(*objInfo));
calculate_world_bounds();
drawP.preupdate_component(&(*objInfo));
}
@ -135,6 +133,8 @@ bool CanvasComponentContainer::collides_with_cam_coords(const CoordSpaceHelper&
// We could just send one of the checkAgainst colliders, since they represent the same thing, but sending both saves on redundant transformations, since we can save both of the versions on the executor side
bool CanvasComponentContainer::collides_with(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld, const SCollision::ColliderCollection<float>& checkAgainstCam) const {
if(!worldAABB.has_value())
return false;
if((camCoords.inverseScale << COMP_MAX_SHIFT_BEFORE_STOP_COLLISIONS) < coords.inverseScale) // Object is too large, just dismiss the collision
return false;
else if(coords.inverseScale < (camCoords.inverseScale >> COMP_COLLIDE_MIN_SHIFT_TINY))
@ -158,7 +158,7 @@ void CanvasComponentContainer::canvas_do_transform(SkCanvas* canvas, const Trans
}
bool CanvasComponentContainer::should_draw(const DrawData& drawData) const {
return (!drawData.clampDrawBetween || (drawData.clampDrawMinimum < coords.inverseScale)) && SCollision::collide(worldAABB.value(), drawData.cam.viewingAreaGenerousCollider) && get_comp().should_draw_extra(drawData, coords);
return (!drawData.clampDrawBetween || (drawData.clampDrawMinimum < coords.inverseScale)) && worldAABB.has_value() && SCollision::collide(worldAABB.value(), drawData.cam.viewingAreaGenerousCollider) && get_comp().should_draw_extra(drawData, coords);
}
CanvasComponentContainer::PreDrawData CanvasComponentContainer::calculate_predraw_data(const DrawData& drawData) const {
@ -195,10 +195,13 @@ unsigned CanvasComponentContainer::get_mipmap_level(const DrawData& drawData) co
void CanvasComponentContainer::calculate_world_bounds() {
worldAABB = coords.collider_to_world<SCollision::AABB<WorldScalar>, SCollision::AABB<float>>(get_comp().get_obj_coord_bounds());
WorldVec dim = worldAABB.value().dim();
if(dim.x() == WorldScalar(0) || dim.y() == WorldScalar(0))
worldAABB = std::nullopt;
}
SCollision::AABB<WorldScalar> CanvasComponentContainer::get_world_bounds() const {
return worldAABB.value();
const std::optional<SCollision::AABB<WorldScalar>>& CanvasComponentContainer::get_world_bounds() const {
return worldAABB;
}
CanvasComponent& CanvasComponentContainer::get_comp() const {

View file

@ -55,7 +55,7 @@ class CanvasComponentContainer {
void save_file(cereal::PortableBinaryOutputArchive& a) const;
void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version, NetworkingObjects::NetObjManager& objMan);
CanvasComponent& get_comp() const;
SCollision::AABB<WorldScalar> get_world_bounds() const;
const std::optional<SCollision::AABB<WorldScalar>>& get_world_bounds() const;
void draw(SkCanvas* canvas, const DrawData& drawData) const;
void draw_with_predraw_data(SkCanvas* canvas, const DrawData& drawData, const PreDrawData& preDrawData) const;
PreDrawData calculate_predraw_data(const DrawData& drawData) const;

View file

@ -130,7 +130,8 @@ void ImageCanvasComponent::update(DrawingProgram& drawP) {
if(display) {
if(display->update_draw())
drawP.invalidate_cache_at_component(&(*compContainer->objInfo));
display->camera_view_update(compContainer->coords, compContainer->get_world_bounds(), drawP.world.drawData, rect_from_points(d.p1, d.p2));
if(compContainer->get_world_bounds().has_value())
display->camera_view_update(compContainer->coords, compContainer->get_world_bounds().value(), drawP.world.drawData, rect_from_points(d.p1, d.p2));
}
}

View file

@ -527,7 +527,7 @@ void DrawingProgram::pen_tool_switch_check() {
void DrawingProgram::invalidate_cache_at_component(CanvasComponentContainer::ObjInfo* objToCheck) {
if(!selection.is_selected(objToCheck))
drawCache.invalidate_cache_at_aabb(objToCheck->obj->get_world_bounds());
drawCache.invalidate_cache_at_optional_aabb(objToCheck->obj->get_world_bounds());
}
void DrawingProgram::preupdate_component(CanvasComponentContainer::ObjInfo* objToCheck) {

View file

@ -32,7 +32,7 @@ DrawingProgramCache::DrawingProgramCache(DrawingProgram& initDrawP):
void DrawingProgramCache::add_component(CanvasComponentContainer::ObjInfo* c) {
unsortedComponents.emplace_back(c);
invalidate_cache_at_aabb(c->obj->get_world_bounds());
invalidate_cache_at_optional_aabb(c->obj->get_world_bounds());
}
void DrawingProgramCache::erase_component(CanvasComponentContainer::ObjInfo* c) {
@ -43,7 +43,7 @@ void DrawingProgramCache::erase_component(CanvasComponentContainer::ObjInfo* c)
}
else
std::erase(unsortedComponents, c);
invalidate_cache_at_aabb(c->obj->get_world_bounds());
invalidate_cache_at_optional_aabb(c->obj->get_world_bounds());
}
void DrawingProgramCache::invalidate_cache_at_aabb(const SCollision::AABB<WorldScalar>& aabb) {
@ -67,6 +67,11 @@ void DrawingProgramCache::invalidate_cache_at_aabb(const SCollision::AABB<WorldS
}
}
void DrawingProgramCache::invalidate_cache_at_optional_aabb(const std::optional<SCollision::AABB<WorldScalar>>& aabb) {
if(aabb.has_value())
invalidate_cache_at_aabb(aabb.value());
}
bool DrawingProgramCache::should_rebuild() const {
return (unsortedComponents.size() >= MINIMUM_COMPONENTS_TO_START_REBUILD);
}
@ -97,11 +102,17 @@ void DrawingProgramCache::build(const std::unordered_set<CanvasComponentContaine
}
void DrawingProgramCache::internal_build(std::vector<CanvasComponentContainer::ObjInfo*> componentsToBuild, const std::unordered_set<CanvasComponentContainer::ObjInfo*>& objsToNotInclude) {
std::erase_if(componentsToBuild, [&objsToNotInclude](auto& c) {
return objsToNotInclude.contains(c);
});
bvhRoot = std::make_shared<DrawingProgramCacheBVHNode>();
unsortedComponents.clear();
std::erase_if(componentsToBuild, [&unsortedComponents = unsortedComponents, &objsToNotInclude](auto& c) {
if(objsToNotInclude.contains(c))
return true;
if(!c->obj->get_world_bounds().has_value()) {
unsortedComponents.emplace_back(c);
return true;
}
return false;
});
clear_own_cached_surfaces();
build_bvh_node(bvhRoot, componentsToBuild);
}
@ -156,16 +167,16 @@ void DrawingProgramCache::preupdate_component(CanvasComponentContainer::ObjInfo*
std::erase(cacheParentBvhNodeLock->components, c);
c->obj->cacheParentBvhNode.reset();
}
invalidate_cache_at_aabb(c->obj->get_world_bounds());
invalidate_cache_at_optional_aabb(c->obj->get_world_bounds());
}
void DrawingProgramCache::build_bvh_node(const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode, const std::vector<CanvasComponentContainer::ObjInfo*>& components) {
if(components.empty())
return;
bvhNode->bounds = components.front()->obj->get_world_bounds();
bvhNode->bounds = components.front()->obj->get_world_bounds().value();
for(auto& c : components)
bvhNode->bounds.include_aabb_in_bounds(c->obj->get_world_bounds());
bvhNode->bounds.include_aabb_in_bounds(c->obj->get_world_bounds().value());
build_bvh_node_coords_and_resolution(*bvhNode);
@ -181,7 +192,7 @@ void DrawingProgramCache::build_bvh_node(const std::shared_ptr<DrawingProgramCac
std::array<std::vector<CanvasComponentContainer::ObjInfo*>, 4> parts;
for(auto& c : components) {
const auto& cAABB = c->obj->get_world_bounds();
const auto& cAABB = c->obj->get_world_bounds().value();
if(cAABB.min.x() < boundsCenter.x() && cAABB.max.x() < boundsCenter.x() && cAABB.min.y() < boundsCenter.y() && cAABB.max.y() < boundsCenter.y())
parts[0].emplace_back(c);
else if(cAABB.min.x() > boundsCenter.x() && cAABB.max.x() > boundsCenter.x() && cAABB.min.y() < boundsCenter.y() && cAABB.max.y() < boundsCenter.y())
@ -425,14 +436,14 @@ void DrawingProgramCache::recursive_draw_layer_item_to_canvas(const DrawingProgr
std::vector<CanvasComponentContainer::ObjInfo*> compsToDraw;
parallel_loop_container(nodesToDraw, [&](auto& node) {
std::for_each(node->components.begin(), node->components.end(), [&](auto& c) {
if(c->obj->parentLayer == &layerListItem && (!drawBounds.has_value() || SCollision::collide(drawBounds.value(), c->obj->get_world_bounds())) && c->obj->should_draw(drawData))
if(c->obj->parentLayer == &layerListItem && (!drawBounds.has_value() || SCollision::collide(drawBounds.value(), c->obj->get_world_bounds().value())) && c->obj->should_draw(drawData))
c->obj->preDrawDataHolder = c->obj->calculate_predraw_data(drawData);
else
c->obj->preDrawDataHolder = std::nullopt;
});
});
parallel_loop_container(unsortedComponents, [&](auto& c) {
if(c->obj->parentLayer == &layerListItem && (!drawBounds.has_value() || SCollision::collide(drawBounds.value(), c->obj->get_world_bounds())) && c->obj->should_draw(drawData))
if(c->obj->parentLayer == &layerListItem && c->obj->get_world_bounds().has_value() && (!drawBounds.has_value() || SCollision::collide(drawBounds.value(), c->obj->get_world_bounds().value())) && c->obj->should_draw(drawData))
c->obj->preDrawDataHolder = c->obj->calculate_predraw_data(drawData);
else
c->obj->preDrawDataHolder = std::nullopt;

View file

@ -38,6 +38,7 @@ class DrawingProgramCache {
void update_and_draw_cached_canvas(SkCanvas* canvas, const DrawData& drawData);
void draw_components_to_canvas(SkCanvas* canvas, const DrawData& drawData, const std::optional<SCollision::AABB<WorldScalar>>& drawBounds);
void invalidate_cache_at_aabb(const SCollision::AABB<WorldScalar>& aabb);
void invalidate_cache_at_optional_aabb(const std::optional<SCollision::AABB<WorldScalar>>& aabb);
static void delete_all_draw_cache();
CanvasComponentContainer::ObjInfo* get_front_object_colliding_with_in_editing_layer(const SCollision::ColliderCollection<float>& cC);

View file

@ -197,7 +197,7 @@ std::function<bool(const std::shared_ptr<DrawingProgramCacheBVHNode>&)> DrawingP
drawP.drawCache.node_loop_erase_if_components(bvhNode, [&](auto c) {
if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, cCWorld, cC)) {
selectedComponents.emplace_back(c);
drawP.drawCache.invalidate_cache_at_aabb(c->obj->get_world_bounds());
drawP.drawCache.invalidate_cache_at_optional_aabb(c->obj->get_world_bounds());
return true;
}
return false;
@ -232,9 +232,9 @@ void DrawingProgramSelection::sort_selection() {
void DrawingProgramSelection::calculate_aabb() {
if(is_something_selected()) {
initialSelectionAABB = (*selectedSet.begin())->obj->get_world_bounds();
initialSelectionAABB = (*selectedSet.begin())->obj->get_world_bounds().value();
for(auto& c : selectedSet)
initialSelectionAABB.include_aabb_in_bounds(c->obj->get_world_bounds());
initialSelectionAABB.include_aabb_in_bounds(c->obj->get_world_bounds().value());
rotateData.centerPos = initialSelectionAABB.center();
}
}

View file

@ -2,7 +2,9 @@
#include "DrawingProgramLayerManager.hpp"
#include "../DrawingProgram.hpp"
#include "../../World.hpp"
#include <Helpers/NetworkingObjects/NetObjOrderedList.hpp>
#include <Helpers/Parallel.hpp>
#include <Helpers/Logger.hpp>
void DrawingProgramLayer::draw(SkCanvas* canvas, const DrawData& drawData) const {
for(auto& p : *components)
@ -32,7 +34,7 @@ void DrawingProgramLayer::set_component_list_callbacks(DrawingProgramLayerListIt
components->set_insert_callback(insertCallback);
components->set_erase_callback(eraseCallback);
components->set_move_callback([&](const CanvasComponentContainer::ObjInfoIterator& c, uint32_t oldPos) {
layerMan.drawP.drawCache.invalidate_cache_at_aabb(c->obj->get_world_bounds());
layerMan.drawP.drawCache.invalidate_cache_at_optional_aabb(c->obj->get_world_bounds());
if(layerMan.drawP.selection.is_selected(&(*c)))
layerMan.drawP.selection.sort_selection(); // If item is selected, selected items will be unordered, so sort everything again
});
@ -67,6 +69,18 @@ void DrawingProgramLayer::load_file(cereal::PortableBinaryInputArchive& a, Versi
}
}
void DrawingProgramLayer::erase_invalid_components() {
std::vector<NetworkingObjects::NetObjOrderedListIterator<CanvasComponentContainer>> compsToErase;
for(auto it = components->begin(); it != components->end(); ++it) {
if(!it->obj->get_world_bounds().has_value())
compsToErase.emplace_back(it);
}
if(!compsToErase.empty()) {
Logger::get().log("INFO", "[DrawingProgramLayer::erase_invalid_components] Erased " + std::to_string(compsToErase.size()) + " invalid component(s) from layer");
components->erase_list(components, compsToErase);
}
}
void DrawingProgramLayer::save_file(cereal::PortableBinaryOutputArchive& a) const {
a(components->size());
for(auto& comp : *components)

View file

@ -25,6 +25,7 @@ class DrawingProgramLayer {
void save_file(cereal::PortableBinaryOutputArchive& a) const;
void get_used_resources(std::unordered_set<NetworkingObjects::NetObjID>& resourceSet) const;
void erase_invalid_components();
private:
std::function<void(const CanvasComponentContainer::ObjInfoIterator& c)> eraseCallback;
};

View file

@ -75,6 +75,11 @@ void DrawingProgramLayerFolder::save_file(cereal::PortableBinaryOutputArchive& a
listItem.obj->save_file(a);
}
void DrawingProgramLayerFolder::erase_invalid_components() {
for(auto& listItem : *folderList)
listItem.obj->erase_invalid_components();
}
void DrawingProgramLayerFolder::get_used_resources(std::unordered_set<NetworkingObjects::NetObjID>& resourceSet) const {
for(auto& listItem : *folderList)
listItem.obj->get_used_resources(resourceSet);

View file

@ -23,4 +23,5 @@ class DrawingProgramLayerFolder {
void save_file(cereal::PortableBinaryOutputArchive& a) const;
void get_used_resources(std::unordered_set<NetworkingObjects::NetObjID>& resourceSet) const;
void erase_invalid_components();
};

View file

@ -271,6 +271,13 @@ DrawingProgramLayerListItemMetaInfo DrawingProgramLayerListItem::get_metainfo()
};
}
void DrawingProgramLayerListItem::erase_invalid_components() {
if(folderData)
folderData->erase_invalid_components();
else
layerData->erase_invalid_components();
}
void DrawingProgramLayerListItem::register_class(World& w) {
w.netObjMan.register_class<DrawingProgramLayerListItem, DrawingProgramLayerListItem, DrawingProgramLayerListItem, DrawingProgramLayerListItem>({
.writeConstructorFuncClient = write_constructor_func,

View file

@ -66,6 +66,8 @@ class DrawingProgramLayerListItem {
DrawingProgramLayerListItemMetaInfo get_metainfo() const;
uint32_t get_component_count() const;
void erase_invalid_components();
private:
static void write_constructor_func(const NetworkingObjects::NetObjTemporaryPtr<DrawingProgramLayerListItem>& o, cereal::PortableBinaryOutputArchive& a);
static void read_constructor_func(const NetworkingObjects::NetObjTemporaryPtr<DrawingProgramLayerListItem>& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr<NetServer::ClientData>& c);

View file

@ -284,6 +284,7 @@ void DrawingProgramLayerManager::load_file(cereal::PortableBinaryInputArchive& a
parallel_loop_container(flattenedCompList, [&drawP = drawP](CanvasComponentContainer::ObjInfo* comp) {
comp->obj->commit_update_dont_invalidate_cache(drawP);
});
layerTreeRoot->erase_invalid_components();
drawP.rebuild_cache();
editingLayer = layerTreeRoot->get_folder().get_initial_editing_layer();
}

View file

@ -162,7 +162,12 @@ void BrushTool::commit_stroke() {
NetworkingObjects::NetObjOwnerPtr<CanvasComponentContainer>& containerPtr = objInfoBeingEdited->obj;
containerPtr->commit_update(drawP);
containerPtr->send_comp_update(drawP, true);
drawP.layerMan.add_undo_place_component(objInfoBeingEdited);
if(containerPtr->get_world_bounds().has_value())
drawP.layerMan.add_undo_place_component(objInfoBeingEdited);
else {
auto& components = containerPtr->parentLayer->get_layer().components;
components->erase(components, containerPtr->objInfo);
}
objInfoBeingEdited = nullptr;
}
}

View file

@ -115,7 +115,12 @@ void EllipseDrawTool::commit() {
NetworkingObjects::NetObjOwnerPtr<CanvasComponentContainer>& containerPtr = objInfoBeingEdited->obj;
containerPtr->commit_update(drawP);
containerPtr->send_comp_update(drawP, true);
drawP.layerMan.add_undo_place_component(objInfoBeingEdited);
if(containerPtr->get_world_bounds().has_value())
drawP.layerMan.add_undo_place_component(objInfoBeingEdited);
else {
auto& components = containerPtr->parentLayer->get_layer().components;
components->erase(components, containerPtr->objInfo);
}
objInfoBeingEdited = nullptr;
}
}

View file

@ -89,7 +89,7 @@ void EraserTool::erase_between_points(const Vector2f& start, const Vector2f& end
drawP.drawCache.node_loop_erase_if_components(bvhNode, [&](auto c) {
if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, cCWorld, cC)) {
erasedComponents.emplace(c);
drawP.drawCache.invalidate_cache_at_aabb(c->obj->get_world_bounds());
drawP.drawCache.invalidate_cache_at_optional_aabb(c->obj->get_world_bounds());
return true;
}
return false;

View file

@ -110,7 +110,12 @@ void LineDrawTool::commit() {
NetworkingObjects::NetObjOwnerPtr<CanvasComponentContainer>& containerPtr = objInfoBeingEdited->obj;
containerPtr->commit_update(drawP);
containerPtr->send_comp_update(drawP, true);
drawP.layerMan.add_undo_place_component(objInfoBeingEdited);
if(containerPtr->get_world_bounds().has_value())
drawP.layerMan.add_undo_place_component(objInfoBeingEdited);
else {
auto& components = containerPtr->parentLayer->get_layer().components;
components->erase(components, containerPtr->objInfo);
}
objInfoBeingEdited = nullptr;
}
}

View file

@ -119,7 +119,12 @@ void RectDrawTool::commit() {
NetworkingObjects::NetObjOwnerPtr<CanvasComponentContainer>& containerPtr = objInfoBeingEdited->obj;
containerPtr->commit_update(drawP);
containerPtr->send_comp_update(drawP, true);
drawP.layerMan.add_undo_place_component(objInfoBeingEdited);
if(containerPtr->get_world_bounds().has_value())
drawP.layerMan.add_undo_place_component(objInfoBeingEdited);
else {
auto& components = containerPtr->parentLayer->get_layer().components;
components->erase(components, containerPtr->objInfo);
}
objInfoBeingEdited = nullptr;
}
}