add prefetching of static data

This commit is contained in:
Rokas Puzonas 2024-08-31 16:56:28 +03:00
parent ee563e6ba6
commit 7d572e7064
4 changed files with 1002 additions and 142 deletions

View File

@ -0,0 +1,25 @@
const std = @import("std");
const assert = std.debug.assert;
pub fn EnumStringUtils(TargetEnum: anytype, str_to_tag_mapping: anytype) type {
if (str_to_tag_mapping.len != @typeInfo(TargetEnum).Enum.fields.len) {
@compileLog("Mapping is not exhaustive");
}
const EnumMapping = std.ComptimeStringMap(TargetEnum, str_to_tag_mapping);
return struct {
pub fn fromString(str: []const u8) ?TargetEnum {
return EnumMapping.get(str);
}
pub fn toString(value: TargetEnum) []const u8 {
inline for (str_to_tag_mapping) |mapping| {
if (mapping[1] == value) {
return mapping[0];
}
}
unreachable;
}
};
}

File diff suppressed because it is too large Load Diff

View File

@ -98,10 +98,7 @@ comptime {
assert(@typeInfo(QueuedAction).Union.fields.len == @typeInfo(QueuedActionResult).Union.fields.len);
}
name: []const u8,
routine: union (enum) {
idle,
pub const CharacterTask = union(enum) {
fight: struct {
at: Position,
target: Server.ItemIdQuantity,
@ -113,16 +110,33 @@ routine: union (enum) {
progress: u64 = 0,
},
craft: struct {
at: Position,
target: Server.ItemIdQuantity,
progress: u64 = 0,
},
},
pub fn isComplete(self: CharacterTask) bool {
return switch (self) {
.fight => |args| {
return args.progress >= args.target.quantity;
},
.gather => |args| {
return args.progress >= args.target.quantity;
},
.craft => |args| {
return args.progress >= args.target.quantity;
}
};
}
};
name: []const u8,
action_queue: std.ArrayList(QueuedAction),
task: ?CharacterTask = null,
pub fn init(allocator: Allocator, name: []const u8) !CharacterBrain {
return CharacterBrain{
.name = try allocator.dupe(u8, name),
.routine = .idle,
.action_queue = std.ArrayList(QueuedAction).init(allocator),
};
}
@ -375,9 +389,9 @@ pub fn performNextAction(self: *CharacterBrain, api: *Server) !void {
}
fn onActionCompleted(self: *CharacterBrain, result: QueuedActionResult) void {
switch (self.routine) {
.idle => {},
if (self.task == null) return;
switch (self.task.?) {
.fight => |*args| {
if (result.get(.fight)) |r| {
const fight_result: Server.FightResult = r;
@ -405,27 +419,21 @@ fn onActionCompleted(self: *CharacterBrain, result: QueuedActionResult) void {
}
}
pub fn isRoutineFinished(self: *CharacterBrain) bool {
return switch (self.routine) {
.idle => false,
pub fn isTaskFinished(self: *CharacterBrain) bool {
if (self.task == null) {
return false;
}
.fight => |args| {
return args.progress >= args.target.quantity;
},
.gather => |args| {
return args.progress >= args.target.quantity;
},
.craft => |args| {
return args.progress >= args.target.quantity;
}
};
return self.task.?.isComplete();
}
pub fn performRoutine(self: *CharacterBrain, api: *Server) !void {
switch (self.routine) {
.idle => {
std.log.debug("[{s}] idle", .{self.name});
},
pub fn performTask(self: *CharacterBrain, api: *Server) !void {
if (self.task == null) {
std.log.debug("[{s}] idle", .{self.name});
return;
}
switch (self.task.?) {
.fight => |args| {
try self.fightRoutine(api, args.at);
},
@ -433,7 +441,7 @@ pub fn performRoutine(self: *CharacterBrain, api: *Server) !void {
try self.gatherRoutine(api, args.at);
},
.craft => |args| {
try self.craftRoutine(api, args.target.id, args.target.quantity);
try self.craftRoutine(api, args.at, args.target.id, args.target.quantity);
}
}
}
@ -541,7 +549,7 @@ fn withdrawFromBank(self: *CharacterBrain, api: *Server, items: []const Server.S
return true;
}
fn craftItem(self: *CharacterBrain, api: *Server, id: Server.ItemId, quantity: u64) !bool {
fn craftItem(self: *CharacterBrain, api: *Server, workstation: Position, id: Server.ItemId, quantity: u64) !bool {
var character = api.findCharacter(self.name).?;
const inventory_quantity = character.inventory.getQuantity(id);
@ -549,24 +557,6 @@ fn craftItem(self: *CharacterBrain, api: *Server, id: Server.ItemId, quantity: u
return false;
}
const code = api.getItemCode(id) orelse return error.InvalidItemId;
const item = try api.getItem(code) orelse return error.ItemNotFound;
if (item.craft == null) {
return error.NotCraftable;
}
const recipe = item.craft.?;
// TODO: Figure this out dynamically
const workstation = switch (recipe.skill) {
.weaponcrafting => Position{ .x = 2, .y = 1 },
.gearcrafting => Position{ .x = 3, .y = 1 },
.jewelrycrafting => Position{ .x = 1, .y = 3 },
.cooking => Position{ .x = 1, .y = 1 },
.woodcutting => Position{ .x = -2, .y = -3 },
.mining => Position{ .x = 1, .y = 5 },
};
if (try self.moveIfNeeded(api, workstation)) {
return true;
}
@ -579,7 +569,7 @@ fn craftItem(self: *CharacterBrain, api: *Server, id: Server.ItemId, quantity: u
return true;
}
fn craftRoutine(self: *CharacterBrain, api: *Server, id: Server.ItemId, quantity: u64) !void {
fn craftRoutine(self: *CharacterBrain, api: *Server, workstation: Position, id: Server.ItemId, quantity: u64) !void {
var character = api.findCharacter(self.name).?;
const inventory_quantity = character.inventory.getQuantity(id);
if (inventory_quantity >= quantity) {
@ -595,7 +585,6 @@ fn craftRoutine(self: *CharacterBrain, api: *Server, id: Server.ItemId, quantity
}
const recipe = target_item.craft.?;
assert(recipe.quantity == 1); // TODO: Add support for recipe which produce multiple items
var needed_items = recipe.items;
for (needed_items.slice()) |*needed_item| {
@ -606,7 +595,7 @@ fn craftRoutine(self: *CharacterBrain, api: *Server, id: Server.ItemId, quantity
return;
}
if (try self.craftItem(api, id, quantity)) {
if (try self.craftItem(api, workstation, id, quantity)) {
return;
}
}

View File

@ -8,6 +8,10 @@ const CharacterBrain = @import("./character_brain.zig");
// pub const std_options = .{ .log_level = .debug };
fn todo() void {
unreachable;
}
fn currentTime() f64 {
const timestamp: f64 = @floatFromInt(std.time.milliTimestamp());
return timestamp / std.time.ms_per_s;
@ -82,6 +86,176 @@ fn getAPITokenFromArgs(allocator: Allocator) !?[]u8 {
return try allocator.dupe(u8, std.mem.trim(u8,token,"\n\t "));
}
const TaskTree = struct {
const TaskTreeNode = struct {
parent: ?usize,
character_task: CharacterBrain.CharacterTask,
};
const Nodes = std.ArrayList(TaskTreeNode);
allocator: Allocator,
nodes: Nodes,
api: *Server,
pub fn init(allocator: Allocator, api: *Server) TaskTree {
return TaskTree{
.allocator = allocator,
.nodes = Nodes.init(allocator),
.api = api
};
}
pub fn deinit(self: TaskTree) void {
self.nodes.deinit();
}
fn appendNode(self: *TaskTree, node: TaskTreeNode) !usize {
try self.nodes.append(node);
return self.nodes.items.len-1;
}
fn appendSubTree(self: *TaskTree, code: []const u8, quantity: u64, parent: ?usize) !void {
if (quantity == 0) return;
const item = (try self.api.getItem(code)).?;
const item_id = try self.api.getItemId(code);
const eql = std.mem.eql;
if (item.craft) |recipe| {
const craft_count = std.math.divCeil(u64, quantity, recipe.quantity) catch unreachable;
const skill_str = Server.SkillUtils.toString(recipe.skill);
const workshop_maps = try self.api.getMaps(.{ .code = skill_str, .type = .workshop });
defer workshop_maps.deinit();
if (workshop_maps.items.len == 0) return error.WorkshopNotFound;
if (workshop_maps.items.len > 1) std.log.warn("Multiple workshop locations exist", .{});
const node_id = try self.appendNode(TaskTreeNode{
.parent = parent,
.character_task = .{
.craft = .{
.at = workshop_maps.items[0].position,
.target = .{ .id = item_id, .quantity = craft_count }
}
}
});
for (recipe.items.slots.constSlice()) |recipe_item| {
const recipe_item_code = self.api.getItemCode(recipe_item.id).?;
try self.appendSubTree(recipe_item_code, recipe_item.quantity * craft_count, node_id);
}
} else {
if (item.type == .resource) {
if (eql(u8, item.subtype, "mining")) {
const resources = try self.api.getResources(.{ .drop = code });
defer resources.deinit();
if (resources.items.len == 0) return error.ResourceNotFound;
if (resources.items.len > 1) std.log.warn("Multiple resources exist for target item", .{});
const resource_code = resources.items[0].code;
const resource_maps = try self.api.getMaps(.{ .code = resource_code });
defer resource_maps.deinit();
if (resource_maps.items.len == 0) return error.MapNotFound;
if (resource_maps.items.len > 1) std.log.warn("Multiple map locations exist for resource", .{});
const resource_map = resource_maps.items[0];
_ = try self.appendNode(TaskTreeNode{
.parent = parent,
.character_task = .{
.gather = .{
.at = resource_map.position,
.target = .{ .id = item_id, .quantity = quantity }
}
}
});
} else if (eql(u8, item.subtype, "mob")) {
const monsters = try self.api.getMonsters(.{ .drop = code });
defer monsters.deinit();
if (monsters.items.len == 0) return error.ResourceNotFound;
if (monsters.items.len > 1) std.log.warn("Multiple monsters exist for target item", .{});
const monster_code = monsters.items[0].code;
const resource_maps = try self.api.getMaps(.{ .code = monster_code });
defer resource_maps.deinit();
if (resource_maps.items.len == 0) return error.MapNotFound;
if (resource_maps.items.len > 1) std.log.warn("Multiple map locations exist for monster", .{});
const resource_map = resource_maps.items[0];
_ = try self.appendNode(TaskTreeNode{
.parent = parent,
.character_task = .{
.fight = .{
.at = resource_map.position,
.target = .{ .id = item_id, .quantity = quantity }
}
}
});
}
} else {
}
}
}
fn listByParent(self: *const TaskTree, parent: ?usize) !std.ArrayList(usize) {
var found_nodes = std.ArrayList(usize).init(self.allocator);
for (0.., self.nodes.items) |i, node| {
if (node.parent == parent) {
try found_nodes.append(i);
}
}
return found_nodes;
}
pub fn format(
self: TaskTree,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
var root_nodes = try self.listByParent(null);
defer root_nodes.deinit();
for (root_nodes.items) |root_node| {
try self.formatNode(root_node, 0, writer);
}
}
fn formatNode(self: TaskTree, node_id: usize, level: u32, writer: anytype) !void {
const node = self.nodes.items[node_id];
try writer.writeBytesNTimes(" ", level);
switch (node.character_task) {
.fight => |args| {
const item = self.api.getItemCode(args.target.id).?;
try writer.print("Task{{ .fight = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, args.target.quantity, args.at});
},
.gather => |args| {
const item = self.api.getItemCode(args.target.id).?;
try writer.print("Task{{ .gather = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, args.target.quantity, args.at});
},
.craft => |args| {
const item = self.api.getItemCode(args.target.id).?;
try writer.print("Task{{ .craft = {{ \"{s}\" x {} }}, .at = {} }}\n", .{item, args.target.quantity, args.at});
},
}
var child_nodes = try self.listByParent(node_id);
defer child_nodes.deinit();
for (child_nodes.items) |child_node| {
try self.formatNode(child_node, level + 1, writer);
}
}
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
@ -90,6 +264,8 @@ pub fn main() !void {
var api = try Server.init(allocator);
defer api.deinit();
try api.prefetch();
const token = (try getAPITokenFromArgs(allocator)) orelse return error.MissingToken;
defer allocator.free(token);
@ -105,7 +281,7 @@ pub fn main() !void {
try goal_manager.addCharacter(char.name);
}
goal_manager.characters.items[0].routine = .{
goal_manager.characters.items[0].task = .{
.fight = .{
.at = Position.init(0, 1),
.target = .{
@ -115,7 +291,7 @@ pub fn main() !void {
},
};
goal_manager.characters.items[1].routine = .{
goal_manager.characters.items[1].task = .{
.gather = .{
.at = Position.init(-1, 0),
.target = .{
@ -125,7 +301,7 @@ pub fn main() !void {
}
};
goal_manager.characters.items[2].routine = .{
goal_manager.characters.items[2].task = .{
.gather = .{
.at = Position.init(2, 0),
.target = .{
@ -135,7 +311,7 @@ pub fn main() !void {
}
};
goal_manager.characters.items[3].routine = .{
goal_manager.characters.items[3].task = .{
.gather = .{
.at = Position.init(4, 2),
.target = .{
@ -145,7 +321,7 @@ pub fn main() !void {
}
};
goal_manager.characters.items[4].routine = .{
goal_manager.characters.items[4].task = .{
.fight = .{
.at = Position.init(0, 1),
.target = .{
@ -155,11 +331,23 @@ pub fn main() !void {
},
};
const APIError = Server.APIError;
var task_tree = TaskTree.init(allocator, &api);
defer task_tree.deinit();
try task_tree.appendSubTree("sticky_dagger", 5, null);
// try task_tree.appendSubTree("copper_boots" , 5, null);
// try task_tree.appendSubTree("copper_helmet", 5, null);
// try task_tree.appendSubTree("copper_legs_armor", 5, null);
// try task_tree.appendSubTree("copper_armor", 5, null);
// try task_tree.appendSubTree("copper_ring", 5, null);
std.debug.print("{}", .{task_tree});
if (false) {
std.log.info("Starting main loop", .{});
while (true) {
goal_manager.runNextAction() catch |err| switch (err) {
APIError.ServerUnavailable => {
Server.APIError.ServerUnavailable => {
// If the server is down, wait for a moment and try again.
std.time.sleep(std.time.ns_per_min * 5);
continue;
@ -174,7 +362,7 @@ pub fn main() !void {
if (brain.isRoutineFinished()) {
if (!try brain.depositItemsToBank(&api)) {
brain.routine = .idle;
brain.routine = null;
}
continue;
}
@ -182,4 +370,5 @@ pub fn main() !void {
try brain.performRoutine(&api);
}
}
}
}