diff --git a/src/errors.zig b/src/errors.zig index 5e5d8f6..6915092 100644 --- a/src/errors.zig +++ b/src/errors.zig @@ -1,3 +1,6 @@ +/// Contains collection of errors connected with an entity (road, node, car, etc.) pub const Entity = error { AlreadyReferenced, + NotFound, + HasReferences, }; \ No newline at end of file diff --git a/src/infrastructure/node.zig b/src/infrastructure/node.zig index dc2c8fb..d46b015 100644 --- a/src/infrastructure/node.zig +++ b/src/infrastructure/node.zig @@ -6,10 +6,14 @@ const e = @import("../errors.zig"); const Road = @import("road.zig").Road; pub const Node = struct { + /// Possibly unnecessary, but for now it's good as a secondary mean of identification id: usize, + /// Node's position on the simulation 'field' pos: rl.Vector2, + /// Contains references of all the roads this node is connected to roads: std.ArrayList(*Road), + /// Simple constructor pub fn init(new_id: usize, new_pos: rl.Vector2) Node { return .{ .id = new_id, @@ -18,20 +22,31 @@ pub const Node = struct { }; } - pub fn deinit(self: *Node, allocator: std.mem.Allocator) void { + /// This function frees the list that holds road references that are connected to this node + /// + /// Next it invalidates itself (the pointer), making it invalid + pub fn deinit(self: *Node, allocator: std.mem.Allocator) !void { + if (self.roads.items.len != 0) return e.Entity.AlreadyReferenced; + self.roads.deinit(allocator); + allocator.destroy(self); } + /// Simple function which draws the node pub fn draw(self: *const Node, direct_colour: ?rl.Color) void { const colour = if (direct_colour) |clr| clr else c.NODE_COLOUR; rl.drawCircleV(self.pos, c.NODE_RADIUS, colour); } + /// Identifies whether the pos (location) is within the snapping radius of the node pub fn posWithinRadius(self: *const Node, pos: rl.Vector2) bool { return rl.checkCollisionPointCircle(pos, self.pos, c.NODE_SNAP_RADIUS); } + /// Tries to reference the passed road to self + /// + /// Returns an error if the passed road cannot be appended to the list or if said road is already in the list pub fn referenceRoad(self: *Node, allocator: std.mem.Allocator, road_to_add: *Road) !void { // Note the road_to_add pointer must be one from the roads list as otherwise the pointer is dangling one for (self.roads.items) |road| { @@ -40,6 +55,22 @@ pub const Node = struct { try self.roads.append(allocator, road_to_add); } + + /// Attempts to unreference the passed road from self (node) + /// + /// Returns whether the node still has node references (true) or not (false) + /// + /// Returns an error if the road is not referenced to self in the first place + pub fn unreferenceRoad(self: *Node, road_to_remove: *const Road) !void { + for (0..self.roads.items.len) |i| { + if (self.roads.items[i] != road_to_remove) continue; + + _ = self.roads.swapRemove(i); + return; + } + + return e.Entity.NotFound; + } }; // TODO tests diff --git a/src/infrastructure/node_manager.zig b/src/infrastructure/node_manager.zig index f31e1d6..f8a244e 100644 --- a/src/infrastructure/node_manager.zig +++ b/src/infrastructure/node_manager.zig @@ -2,6 +2,7 @@ const std = @import("std"); const Vector2 = @import("raylib").Vector2; const c = @import("../constants.zig"); +const e = @import("../errors.zig"); const Node = @import("node.zig").Node; const Road = @import("road.zig").Road; @@ -18,10 +19,9 @@ pub const NodeManager = struct { }; } - pub fn deinit(self: *NodeManager, allocator: std.mem.Allocator) void { + pub fn deinit(self: *NodeManager, allocator: std.mem.Allocator) !void { for (self.nodes.items) |node| { - node.deinit(allocator); - allocator.destroy(node); + try node.deinit(allocator); } self.nodes.deinit(allocator); } @@ -36,7 +36,7 @@ pub const NodeManager = struct { var cur_node = Node.init(0, pos); // Temporary road that is to be drawn as one in the making const road: Road = .init(0, node, &cur_node); - road.draw(); + road.draw(false); node.*.draw(c.NODE_TEMP_COLOUR); cur_node.draw(c.NODE_CURSOR_COLOUR); @@ -64,15 +64,28 @@ pub const NodeManager = struct { return id; } - pub fn clear(self: *NodeManager, allocator: std.mem.Allocator) void { + pub fn clear(self: *NodeManager, allocator: std.mem.Allocator) !void { self.temp_node = null; for (self.nodes.items) |node| { - node.deinit(allocator); - allocator.destroy(node); + try node.deinit(allocator); } self.nodes.clearRetainingCapacity(); self.next_id = 0; } + + pub fn deleteNode(self: *NodeManager, allocator: std.mem.Allocator, node_to_delete: *Node) !void { + if (node_to_delete.roads.items.len != 0) return e.Entity.HasReferences; + + for (0..self.nodes.items.len) |i| { + if (self.nodes.items[i] != node_to_delete) continue; + + try node_to_delete.deinit(allocator); + _ = self.nodes.swapRemove(i); + return; + } + + return e.Entity.NotFound; + } }; const expect = std.testing.expect; @@ -101,4 +114,5 @@ test "id tracking" { } // TODO tests -// force resize pointer test \ No newline at end of file +// force resize pointer test +// deleting nodes \ No newline at end of file diff --git a/src/infrastructure/road.zig b/src/infrastructure/road.zig index 69717d2..2d407f9 100644 --- a/src/infrastructure/road.zig +++ b/src/infrastructure/road.zig @@ -11,6 +11,7 @@ pub const Road = struct { /// Calculated road length length: f32, + /// Constructor, which sets nodes for the road and calculates its length pub fn init(new_id: usize, start: *Node, end: *Node) Road { var road: Road = .{ .id = new_id, @@ -22,6 +23,11 @@ pub const Road = struct { return road; } + /// This makes the road ptr DEAD + pub fn deinit(self: *Road, allocator: std.mem.Allocator) void { + allocator.destroy(self); + } + /// Calculates length of the road by taking its two nodes fn calculate_length(self: *const Road) f32 { const start = self.nodes[0]; @@ -34,8 +40,26 @@ pub const Road = struct { return @sqrt(square_diff); } - pub fn draw(self: *const Road) void { - rl.drawLineEx(self.nodes[0].*.pos, self.nodes[1].*.pos, c.ROAD_SIZE, c.ROAD_COLOUR); + /// Simple function which draws the node + /// + /// In the future as we improve and make roads more complex with multiple lanes and such + /// it will gradually become more complex + pub fn draw(self: *const Road, highlighted: bool) void { + const colour = if (highlighted) c.ROAD_HIGHLIGHTED_COLOUR else c.ROAD_COLOUR; + rl.drawLineEx(self.nodes[0].*.pos, self.nodes[1].*.pos, c.ROAD_SIZE, colour); + } + + /// Important: after this function executes, this road is no longer reachable from its bounding nodes + /// + /// The function unreferences self (road) from its bounding nodes + pub fn unreferenceNodes(self: *const Road) !void { + try self.nodes[0].unreferenceRoad(self); + try self.nodes[1].unreferenceRoad(self); + } + + /// Checks whether pos coordinate is on the referenced road + pub fn isHighlighted(self: *const Road, pos: rl.Vector2) bool { + return rl.checkCollisionPointLine(pos, self.nodes[0].*.pos, self.nodes[1].*.pos, c.ROAD_SIZE); } }; diff --git a/src/infrastructure/road_manager.zig b/src/infrastructure/road_manager.zig index 036b46c..5f5f05f 100644 --- a/src/infrastructure/road_manager.zig +++ b/src/infrastructure/road_manager.zig @@ -1,28 +1,33 @@ const std = @import("std"); + +const e = @import("../errors.zig"); const Road = @import("road.zig").Road; const Node = @import("node.zig").Node; pub const RoadManager = struct { next_id: usize, roads: std.ArrayList(*Road), + highlighted_road: ?*Road, pub fn init() RoadManager { return .{ .next_id = 0, .roads = .empty, + .highlighted_road = null, }; } pub fn deinit(self: *RoadManager, allocator: std.mem.Allocator) void { for (self.roads.items) |road| { - allocator.destroy(road); + road.deinit(allocator); } self.roads.deinit(allocator); } - pub fn draw(self: *const RoadManager) void { + pub fn draw(self: *const RoadManager, delete_mode: bool) void { for (self.roads.items) |road| { - road.draw(); + const is_highlighted = delete_mode and self.highlighted_road != null and self.highlighted_road.? == road; + road.draw(is_highlighted); } } @@ -46,11 +51,40 @@ pub const RoadManager = struct { pub fn clear(self: *RoadManager, allocator: std.mem.Allocator) void { for (self.roads.items) |road| { - allocator.destroy(road); + road.deinit(allocator); } self.roads.clearRetainingCapacity(); self.next_id = 0; } + + pub fn deleteRoad(self: *RoadManager, allocator: std.mem.Allocator, road_to_delete: *Road) !void { + // unreference the road from its bounding functions + road_to_delete.unreferenceNodes() catch |err| { + std.debug.panic("Failed to unreference the road from its nodes: {}\n", .{err}); + }; + + for (0..self.roads.items.len) |i| { + if (self.roads.items[i] != road_to_delete) continue; + + road_to_delete.deinit(allocator); + _ = self.roads.swapRemove(i); + return; + } + + return e.Entity.NotFound; + } + + /// Sets the pointer to the road that is intersection with pos coordinate + pub fn update_highlighted_road(self: *RoadManager, pos: Vector2) void { + for (self.roads.items) |road| { + if (!road.isHighlighted(pos)) continue; + + self.highlighted_road = road; + return; + } + + self.highlighted_road = null; + } }; const Vector2 = @import("raylib").Vector2; @@ -91,4 +125,5 @@ test "id tracking" { // TODO tests // force resize pointer test +// add, remove road // destroy road and then verify the nodes do not have a pointer to it \ No newline at end of file diff --git a/src/main.zig b/src/main.zig index 27991ee..e199448 100644 --- a/src/main.zig +++ b/src/main.zig @@ -19,7 +19,9 @@ pub fn main(init: std.process.Init) !void { rl.setTargetFPS(rl.getMonitorRefreshRate(monitor)); var sim: Simulator = .init(allocator); - defer sim.deinit(); + defer sim.deinit() catch |err| { + std.debug.panic("Failed to deinitialise the sim: {}\n", .{err}); + }; while (!rl.windowShouldClose()) { rl.beginDrawing(); @@ -27,6 +29,7 @@ pub fn main(init: std.process.Init) !void { const pos = rl.getMousePosition(); sim.handleInput(pos); + sim.update(pos); sim.draw(pos); } diff --git a/src/simulator.zig b/src/simulator.zig index 09539e8..f050c5c 100644 --- a/src/simulator.zig +++ b/src/simulator.zig @@ -11,6 +11,7 @@ pub const Simulator = struct { road_man: RoadManager, // vars auto_continue: bool, + delete_mode: bool, pub fn init(new_allocator: std.mem.Allocator) Simulator { return .{ @@ -18,21 +19,26 @@ pub const Simulator = struct { .node_man = .init(), .road_man = .init(), .auto_continue = false, + .delete_mode = false, }; } - pub fn deinit(self: *Simulator) void { + pub fn deinit(self: *Simulator) !void { self.road_man.deinit(self.allocator); - self.node_man.deinit(self.allocator); + try self.node_man.deinit(self.allocator); } pub fn draw(self: *const Simulator, pos: rl.Vector2) void { rl.clearBackground(c.BACKGROUND_COLOR); - self.road_man.draw(); + self.road_man.draw(self.delete_mode); self.node_man.draw(pos); } + pub fn update(self: *Simulator, pos: rl.Vector2) void { + self.road_man.update_highlighted_road(pos); + } + pub fn handleInput(self: *Simulator, pos: rl.Vector2) void { self.handleKeyboardInput(); self.handleMouseInput(pos); @@ -40,8 +46,11 @@ pub const Simulator = struct { fn handleKeyboardInput(self: *Simulator) void { self.auto_continue = rl.isKeyDown(.left_control); + self.delete_mode = rl.isKeyDown(.left_shift); - if (rl.isKeyReleased(.c)) self.clear(); + if (rl.isKeyReleased(.c)) self.clear() catch |err| { + std.debug.panic("Failed to clear the entities: {}\n", .{err}); + }; } fn handleMouseInput(self: *Simulator, pos: rl.Vector2) void { @@ -49,6 +58,16 @@ pub const Simulator = struct { } fn leftClickEvent(self: *Simulator, pos: rl.Vector2) void { + if (self.delete_mode) { + self.delete_road() catch |err| { + std.debug.panic("Failed to delete the road: {}\n", .{err}); + }; + return; + } + self.new_road(pos); + } + + fn new_road(self: *Simulator, pos: rl.Vector2) void { const cur_node = self.node_man.getSelectedNode(self.allocator, pos) catch |err| { std.debug.panic("Failed to append the newly created node at pos ({d}, {d}) to node list: {}\n", .{ pos.x, pos.y, err @@ -68,8 +87,27 @@ pub const Simulator = struct { self.node_man.temp_node = cur_node; } - fn clear(self: *Simulator) void { + fn delete_road(self: *Simulator) !void { + if (self.road_man.highlighted_road == null) return; + const h_road = self.road_man.highlighted_road.?; + + const start_node = h_road.*.nodes[0]; + const end_node = h_road.*.nodes[1]; + + self.road_man.deleteRoad(self.allocator, h_road) catch |err| { + std.debug.panic("Road deletion failed: {}\n", .{err}); + }; + self.road_man.highlighted_road = null; + + if (start_node.roads.items.len == 0) + try self.node_man.deleteNode(self.allocator, start_node); + + if (end_node.roads.items.len == 0) + try self.node_man.deleteNode(self.allocator, end_node); + } + + fn clear(self: *Simulator) !void { self.road_man.clear(self.allocator); - self.node_man.clear(self.allocator); + try self.node_man.clear(self.allocator); } }; \ No newline at end of file diff --git a/src/test.zig b/src/test.zig index b36ecdf..b0b10ab 100644 --- a/src/test.zig +++ b/src/test.zig @@ -1,3 +1,4 @@ +// Main test file that imports all files that have testing blocks in them test { _ = @import("infrastructure/road.zig"); _ = @import("infrastructure/node_manager.zig");