const std = @import("std"); const Vector2 = @import("raylib").Vector2; const c = @import("../common/constants.zig"); const e = @import("../errors.zig"); const Node = @import("node.zig").Node; const Road = @import("road.zig").Road; pub const NodeManager = struct { /// Tracks which ID the next added node will get next_id: usize, /// Tracks all the node (pointers) nodes: std.ArrayList(*Node), /// Tracks the temporary node, being part of the newly built road temp_node: ?*Node, /// Constructor (for convenience) pub fn init() NodeManager { return .{ .next_id = 0, .nodes = .empty, .temp_node = null, }; } /// Deinitialises every node (pointer) within the list and then deinits the list containing the nodes itself pub fn deinit(self: *NodeManager, allocator: std.mem.Allocator) !void { for (self.nodes.items) |node| { node.roads.clearRetainingCapacity(); try node.deinit(allocator); } self.nodes.deinit(allocator); } /// Regular draw function pub fn draw(self: *const NodeManager, pos: Vector2, display_info: bool) void { for (self.nodes.items) |node| { node.draw(null, display_info); } if (self.temp_node) |node| { // Temporary node that points at the cursor 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(false, false); node.draw(c.NODE_TEMP_COLOUR, display_info); cur_node.draw(c.NODE_CURSOR_COLOUR, false); } } /// Tries to find a node which snapping radius covers the pos /// /// If it does it returns a reference to it, otherwise null pub fn getNodeIfExists(self: *const NodeManager, pos: Vector2) ?*Node { for (self.nodes.items) |node| { if (node.withinSnapRadius(pos)) return node; } return null; } /// Checks if there is a node pointer within the snap radius of pos coordinate; /// otherwise creates a new node and returns its pointer pub fn getSelectedNode(self: *NodeManager, allocator: std.mem.Allocator, pos: Vector2) !*Node { if (self.getNodeIfExists(pos)) |node| { return node; } // No node is within that position, so we must create a new one const node: Node = .init(self.getNextID(), pos); const node_ptr = try allocator.create(Node); node_ptr.* = node; try self.nodes.append(allocator, node_ptr); return self.nodes.items[self.nodes.items.len - 1]; } /// Gets next id, resets only on clear() fn getNextID(self: *NodeManager) usize { const id = self.next_id; self.next_id += 1; return id; } /// Clears all existing nodes connected, not deinitialisation pub fn clear(self: *NodeManager, allocator: std.mem.Allocator) !void { self.temp_node = null; for (self.nodes.items) |node| { node.roads.clearRetainingCapacity(); try node.deinit(allocator); } self.nodes.clearRetainingCapacity(); self.next_id = 0; } /// Deletes node, returns error if node still has road references pub fn deleteNode(self: *NodeManager, allocator: std.mem.Allocator, node_to_delete: *Node) !void { 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; } /// Runs a scan through all the nodes and returns the reference to one that is within the radius pub fn getHighlightedNode(self: *const NodeManager, pos: Vector2) ?*Node { for (self.nodes.items) |node| { if (node.withinRadius(pos)) return node; } return null; } /// Essentially what it does is it sets temp node pointer to null and /// if the node it pointed at had no road references (essentially it was a new node for road building), /// it deletes the node from the node list as well pub fn deleteTempNode(self: *NodeManager, allocator: std.mem.Allocator) void { if (self.temp_node == null) return; const node = self.temp_node.?; self.temp_node = null; if (node.roads.items.len != 0) return; self.deleteNode(allocator, node) catch |err| { std.debug.panic("Failed to delete the temporary node: {}\n", .{err}); }; } }; const expect = std.testing.expect; test "id tracking" { var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const allocator = gpa.allocator(); var node_man: NodeManager = .init(); defer node_man.deinit(allocator); const n = 5; for (0..n) |_| { const node: Node = .init(node_man.getNextID(), Vector2 { .x = 500, .y = 500, }); const node_ptr = try allocator.create(Node); node_ptr.* = node; try node_man.nodes.append(allocator, node_ptr); } try expect(node_man.next_id == n); try expect(node_man.nodes.items.len == n); } // TODO tests // force resize pointer test // deleting nodes