const std = @import("std"); const Api = @import("artifacts-api"); const Allocator = std.mem.Allocator; pub const Brain = @import("./brain.zig"); pub const TaskGraph = @import("./task_graph.zig"); const Artificer = @This(); const expiration_margin: u64 = 100 * std.time.ns_per_ms; // 100ms const server_down_retry_interval = 5; // minutes server: Api.Server, characters: std.ArrayList(Brain), task_graph: TaskGraph, paused_until: ?i64 = null, pub fn init(allocator: Allocator, token: []const u8) !Artificer { var server = try Api.Server.init(allocator); errdefer server.deinit(); try server.setToken(token); var characters = std.ArrayList(Brain).init(allocator); errdefer characters.deinit(); // TODO: Add character deinit const chars = try server.listMyCharacters(); defer chars.deinit(); for (chars.items) |char| { try characters.append(try Brain.init(allocator, char.name)); } return Artificer{ .server = server, .characters = characters, .task_graph = TaskGraph.init(allocator) }; } pub fn deinit(self: *Artificer) void { for (self.characters.items) |brain| { brain.deinit(); } self.characters.deinit(); self.server.deinit(); } pub fn step(self: *Artificer) !void { if (self.paused_until) |paused_until| { if (std.time.timestamp() < paused_until) { return; } self.paused_until = null; } runNextActions(self.characters.items, &self.server) catch |err| switch (err) { Api.FetchError.ServerUnavailable => { self.paused_until = std.time.timestamp() + std.time.ns_per_min * server_down_retry_interval; std.log.warn("Server is down, retrying in {}min", .{ server_down_retry_interval }); return; }, else => return err }; for (self.characters.items) |*brain| { if (brain.task == null) { const character = self.server.store.getCharacter(brain.name).?; if (character.task == null) { brain.task = .{ .accept_task = .{} }; } } try brain.step(&self.server); } } pub fn nextStepAt(self: *Artificer) i64 { if (self.paused_until) |paused_until| { return paused_until; } return earliestCooldown(self.characters.items, &self.server) orelse 0; } fn earliestCooldown(characters: []Brain, api: *Api.Server) ?i64 { var earliest_cooldown: ?i64 = null; for (characters) |*brain| { if (brain.action_queue.items.len == 0) continue; const cooldown = brain.cooldown(api); if (earliest_cooldown == null or earliest_cooldown.? > cooldown) { earliest_cooldown = cooldown; } } return earliest_cooldown; } fn runNextActions(characters: []Brain, api: *Api.Server) !void { const maybe_earliest_cooldown = earliestCooldown(characters, api); if (maybe_earliest_cooldown == null) return; const earliest_cooldown = maybe_earliest_cooldown.?; if (earliest_cooldown < std.time.timestamp()) return; for (characters) |*brain| { if (brain.action_queue.items.len == 0) continue; const cooldown = brain.cooldown(api); if (earliest_cooldown > cooldown) { try brain.performNextAction(api); } } }