add cached prefetch

This commit is contained in:
Rokas Puzonas 2024-09-08 13:09:32 +03:00
parent 429b1dcd6e
commit f5a4ba4503
11 changed files with 228 additions and 70 deletions

View File

@ -4,6 +4,7 @@ pub const parseDateTime = @import("./date_time/parse.zig").parseDateTime;
pub const Server = @import("server.zig"); pub const Server = @import("server.zig");
pub const Store = @import("store.zig"); pub const Store = @import("store.zig");
pub const Character = @import("./schemas/character.zig"); pub const Character = @import("./schemas/character.zig");
pub const ServerStatus = @import("./schemas/status.zig");
pub const Map = @import("./schemas/map.zig"); pub const Map = @import("./schemas/map.zig");
pub const Position = @import("position.zig"); pub const Position = @import("position.zig");
pub const BoundedSlotsArray = @import("schemas/slot_array.zig").BoundedSlotsArray; pub const BoundedSlotsArray = @import("schemas/slot_array.zig").BoundedSlotsArray;

View File

@ -27,7 +27,7 @@ pub fn parse(store: *Store, obj: json.ObjectMap) !DropRate {
} }
const code_str = try json_utils.getStringRequired(obj, "code"); const code_str = try json_utils.getStringRequired(obj, "code");
const item_id = try store.getItemId(code_str); const item_id = try store.getCodeId(code_str);
return DropRate{ return DropRate{
.item_id = item_id, .item_id = item_id,

View File

@ -38,9 +38,8 @@ pub const TypeUtils = EnumStringUtils(Type, .{
.{ "currency" , .currency }, .{ "currency" , .currency },
}); });
allocator: Allocator,
name: []u8, name: []u8,
code: []u8, code_id: Store.CodeId,
level: u64, level: u64,
type: Type, type: Type,
subtype: []u8, subtype: []u8,
@ -56,9 +55,8 @@ pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !Item {
const item_type_str = try json_utils.getStringRequired(obj, "type"); const item_type_str = try json_utils.getStringRequired(obj, "type");
return Item{ return Item{
.allocator = allocator,
.name = (try json_utils.dupeString(allocator, obj, "name")) orelse return error.MissingProperty, .name = (try json_utils.dupeString(allocator, obj, "name")) orelse return error.MissingProperty,
.code = (try json_utils.dupeString(allocator, obj, "code")) orelse return error.MissingProperty, .code_id = (try store.getCodeIdJson(obj, "code")) orelse return error.MissingProperty,
.level = @intCast(level), .level = @intCast(level),
.type = TypeUtils.fromString(item_type_str) orelse return error.InvalidType, .type = TypeUtils.fromString(item_type_str) orelse return error.InvalidType,
.subtype = (try json_utils.dupeString(allocator, obj, "subtype")) orelse return error.MissingProperty, .subtype = (try json_utils.dupeString(allocator, obj, "subtype")) orelse return error.MissingProperty,
@ -67,9 +65,8 @@ pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !Item {
}; };
} }
pub fn deinit(self: Item) void { pub fn deinit(self: Item, allocator: Allocator) void {
self.allocator.free(self.name); allocator.free(self.name);
self.allocator.free(self.code); allocator.free(self.subtype);
self.allocator.free(self.subtype); allocator.free(self.description);
self.allocator.free(self.description);
} }

View File

@ -59,10 +59,10 @@ pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !Monster
} }
return Monster{ return Monster{
.name = try json_utils.dupeStringRequired(allocator, obj, "name"), .name = try json_utils.dupeStringRequired(allocator, obj, "name"),
.code = (try store.getCodeIdJson(allocator, obj, "code")) orelse return error.MissingProperty, .code_id = (try store.getCodeIdJson(obj, "code")) orelse return error.MissingProperty,
.level = @intCast(level), .level = @intCast(level),
.hp = @intCast(hp), .hp = @intCast(hp),
.fire = try ElementalStats.parse(obj, "attack_fire" , "res_fire" ), .fire = try ElementalStats.parse(obj, "attack_fire" , "res_fire" ),
.earth = try ElementalStats.parse(obj, "attack_earth", "res_earth"), .earth = try ElementalStats.parse(obj, "attack_earth", "res_earth"),

View File

@ -38,11 +38,11 @@ pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !Resource
const skill_str = try json_utils.getStringRequired(obj, "skill"); const skill_str = try json_utils.getStringRequired(obj, "skill");
return Resource{ return Resource{
.name = try json_utils.dupeStringRequired(allocator, obj, "name"), .name = try json_utils.dupeStringRequired(allocator, obj, "name"),
.code = (try store.getCodeIdJson(allocator, obj, "code")) orelse return error.MissingProperty, .code_id = (try store.getCodeIdJson(obj, "code")) orelse return error.MissingProperty,
.level = @intCast(level), .level = @intCast(level),
.skill = SkillUtils.fromString(skill_str) orelse return error.InvalidSkill, .skill = SkillUtils.fromString(skill_str) orelse return error.InvalidSkill,
.drops = try DropRate.parseList(store, drops_array) .drops = try DropRate.parseList(store, drops_array)
}; };
} }

View File

@ -407,6 +407,26 @@ pub fn prefetch(self: *Server) !void {
self.prefetched = true; self.prefetched = true;
} }
pub fn prefetchCached(self: *Server, cache_path: []const u8) !void {
var status: ServerStatus = try self.getServerStatus();
defer status.deinit();
if (std.fs.openFileAbsolute(cache_path, .{})) |file| {
defer file.close();
if (self.store.load(status.version, file.reader())) {
self.prefetched = true;
return;
} else |_| { }
} else |_| { }
try self.prefetch();
const file = try std.fs.createFileAbsolute(cache_path, .{});
defer file.close();
try self.store.save(status.version, file.writer());
}
// ------------------------- Endpoints ------------------------ // ------------------------- Endpoints ------------------------
pub fn getServerStatus(self: *Server) FetchError!ServerStatus { pub fn getServerStatus(self: *Server) FetchError!ServerStatus {

View File

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const json_utils = @import("json_utils.zig"); const json_utils = @import("json_utils.zig");
const Server = @import("./server.zig"); const Server = @import("./server.zig");
const s2s = @import("s2s");
const json = std.json; const json = std.json;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const assert = std.debug.assert; const assert = std.debug.assert;
@ -17,61 +18,48 @@ const DropRate = @import("./schemas/drop_rate.zig");
pub const CodeId = u16; pub const CodeId = u16;
const Characters = std.ArrayList(Character);
const ItemsMap = std.StringHashMap(Item);
const MapsMap = std.AutoHashMap(Position, Map);
const ResourcesMap = std.StringHashMap(Resource);
const MonstersMap = std.StringHashMap(Monster);
allocator: Allocator, allocator: Allocator,
codes: std.ArrayList([]u8), codes: std.ArrayList([]u8),
characters: std.ArrayList(Character), characters: Characters,
items: std.StringHashMap(Item), items: ItemsMap,
maps: std.AutoHashMap(Position, Map), maps: MapsMap,
resources: std.StringHashMap(Resource), resources: ResourcesMap,
monsters: std.StringHashMap(Monster), monsters: MonstersMap,
// TODO: bank // TODO: bank
pub fn init(allocator: Allocator) Store { pub fn init(allocator: Allocator) Store {
return Store{ return Store{
.allocator = allocator, .allocator = allocator,
.codes = std.ArrayList([]u8).init(allocator), .codes = std.ArrayList([]u8).init(allocator),
.characters = std.ArrayList(Character).init(allocator), .characters = Characters.init(allocator),
.items = std.StringHashMap(Item).init(allocator), .items = ItemsMap.init(allocator),
.maps = std.AutoHashMap(Position, Map).init(allocator), .maps = MapsMap.init(allocator),
.resources = std.StringHashMap(Resource).init(allocator), .resources = ResourcesMap.init(allocator),
.monsters = std.StringHashMap(Monster).init(allocator), .monsters = MonstersMap.init(allocator),
}; };
} }
pub fn deinit(self: *Store) void { pub fn deinit(self: *Store) void {
for (self.codes.items) |code| { self.clearCodes();
self.allocator.free(code);
}
self.codes.deinit();
for (self.characters.items) |*char| { for (self.characters.items) |*char| {
char.deinit(); char.deinit();
} }
self.characters.deinit(); self.characters.deinit();
var itemsIter = self.items.valueIterator(); self.clearItems();
while (itemsIter.next()) |item| {
item.deinit();
}
self.items.deinit();
var mapsIter = self.maps.valueIterator(); self.clearMaps();
while (mapsIter.next()) |map| {
map.deinit(self.allocator);
}
self.maps.deinit();
var resourcesIter = self.resources.valueIterator(); self.clearResources();
while (resourcesIter.next()) |resource| {
resource.deinit(self.allocator);
}
self.resources.deinit();
var monstersIter = self.monsters.valueIterator(); self.clearMonsters();
while (monstersIter.next()) |monster| {
monster.deinit(self.allocator);
}
self.monsters.deinit();
} }
pub fn getCodeId(self: *Store, code: []const u8) !CodeId { pub fn getCodeId(self: *Store, code: []const u8) !CodeId {
@ -107,6 +95,101 @@ pub fn getCodeIdJson(self: *Store, object: json.ObjectMap, name: []const u8) !?C
return try self.getCodeId(code); return try self.getCodeId(code);
} }
fn clearCodes(self: *Store) void {
for (self.codes.items) |code| {
self.allocator.free(code);
}
self.codes.clearAndFree();
}
// ----------------------- Storing to file ------------------------------
const SaveData = struct {
api_version: []const u8,
codes: [][]u8,
items: []Item,
maps: []Map,
resources: []Resource,
monsters: []Monster,
};
fn allocHashMapValues(Value: type, allocator: Allocator, hashmap: anytype) ![]Value {
var values = try allocator.alloc(Value, hashmap.count());
errdefer allocator.free(values);
var valueIter = hashmap.valueIterator();
var index: usize = 0;
while (valueIter.next()) |value| {
values[index] = value.*;
index += 1;
}
return values;
}
pub fn save(self: *Store, api_version: []const u8, writer: anytype) !void {
const items = try allocHashMapValues(Item, self.allocator, self.items);
defer self.allocator.free(items);
const maps = try allocHashMapValues(Map, self.allocator, self.maps);
defer self.allocator.free(maps);
const resources = try allocHashMapValues(Resource, self.allocator, self.resources);
defer self.allocator.free(resources);
const monsters = try allocHashMapValues(Monster, self.allocator, self.monsters);
defer self.allocator.free(monsters);
const data = SaveData{
.api_version = api_version,
.codes = self.codes.items,
.items = items,
.maps = maps,
.resources = resources,
.monsters = monsters
};
try s2s.serialize(writer, SaveData, data);
}
pub fn load(self: *Store, api_version: []const u8, reader: anytype) !void {
var data = try s2s.deserializeAlloc(reader, SaveData, self.allocator);
if (!std.mem.eql(u8, data.api_version, api_version)) {
s2s.free(self.allocator, SaveData, &data);
return error.InvalidVersion;
}
defer self.allocator.free(data.api_version);
self.clearCodes();
try self.codes.appendSlice(data.codes);
defer self.allocator.free(data.codes);
self.clearItems();
for (data.items) |item| {
try self.putItem(item);
}
defer self.allocator.free(data.items);
self.clearMaps();
for (data.maps) |map| {
try self.putMap(map);
}
defer self.allocator.free(data.maps);
self.clearResources();
for (data.resources) |resource| {
try self.putResource(resource);
}
defer self.allocator.free(data.resources);
self.clearMonsters();
for (data.monsters) |monster| {
try self.putMonster(monster);
}
defer self.allocator.free(data.monsters);
}
// ----------------------- Character ------------------------------ // ----------------------- Character ------------------------------
fn getCharacterIndex(self: *const Store, name: []const u8) ?usize { fn getCharacterIndex(self: *const Store, name: []const u8) ?usize {
@ -174,6 +257,14 @@ pub fn putMap(self: *Store, map: Map) !void {
entry.value_ptr.* = map; entry.value_ptr.* = map;
} }
fn clearMaps(self: *Store) void {
var mapsIter = self.maps.valueIterator();
while (mapsIter.next()) |map| {
map.deinit(self.allocator);
}
self.maps.clearAndFree();
}
// ----------------------- Item ------------------------------ // ----------------------- Item ------------------------------
pub fn getItem(self: *Store, code: []const u8) ?Item { pub fn getItem(self: *Store, code: []const u8) ?Item {
@ -194,7 +285,7 @@ pub fn getItems(self: *Store, opts: Server.ItemOptions) !std.ArrayList(Item) {
if (item.craft == null) continue; if (item.craft == null) continue;
const recipe = item.craft.?; const recipe = item.craft.?;
const craft_material_id = try self.getItemId(craft_material); const craft_material_id = try self.getCodeId(craft_material);
const material_quantity = recipe.items.getQuantity(craft_material_id); const material_quantity = recipe.items.getQuantity(craft_material_id);
if (material_quantity == 0) continue; if (material_quantity == 0) continue;
} }
@ -218,13 +309,22 @@ pub fn getItems(self: *Store, opts: Server.ItemOptions) !std.ArrayList(Item) {
} }
pub fn putItem(self: *Store, item: Item) !void { pub fn putItem(self: *Store, item: Item) !void {
var entry = try self.items.getOrPut(item.code); const code = self.getCode(item.code_id).?;
var entry = try self.items.getOrPut(code);
if (entry.found_existing) { if (entry.found_existing) {
entry.value_ptr.deinit(); entry.value_ptr.deinit(self.allocator);
} }
entry.value_ptr.* = item; entry.value_ptr.* = item;
} }
fn clearItems(self: *Store) void {
var itemsIter = self.items.valueIterator();
while (itemsIter.next()) |item| {
item.deinit(self.allocator);
}
self.items.clearAndFree();
}
// ----------------------- Monster ------------------------------ // ----------------------- Monster ------------------------------
pub fn getMonster(self: *Store, code: []const u8) ?Monster { pub fn getMonster(self: *Store, code: []const u8) ?Monster {
@ -244,7 +344,7 @@ pub fn getMonsters(self: *Store, opts: Server.MonsterOptions) !std.ArrayList(Mon
if (monster.level > max_level) continue; if (monster.level > max_level) continue;
} }
if (opts.drop) |drop| { if (opts.drop) |drop| {
const item_id = try self.getItemId(drop); const item_id = try self.getCodeId(drop);
if (!DropRate.doesListContain(&monster.drops, item_id)) { if (!DropRate.doesListContain(&monster.drops, item_id)) {
continue; continue;
} }
@ -257,13 +357,22 @@ pub fn getMonsters(self: *Store, opts: Server.MonsterOptions) !std.ArrayList(Mon
} }
pub fn putMonster(self: *Store, monster: Monster) !void { pub fn putMonster(self: *Store, monster: Monster) !void {
var entry = try self.resources.getOrPut(monster.code_id); const code = self.getCode(monster.code_id).?;
var entry = try self.monsters.getOrPut(code);
if (entry.found_existing) { if (entry.found_existing) {
entry.value_ptr.deinit(self.allocator); entry.value_ptr.deinit(self.allocator);
} }
entry.value_ptr.* = monster; entry.value_ptr.* = monster;
} }
fn clearMonsters(self: *Store) void {
var monstersIter = self.monsters.valueIterator();
while (monstersIter.next()) |monster| {
monster.deinit(self.allocator);
}
self.monsters.clearAndFree();
}
// ----------------------- Resource ------------------------------ // ----------------------- Resource ------------------------------
pub fn getResource(self: *Store, code: []const u8) ?Resource { pub fn getResource(self: *Store, code: []const u8) ?Resource {
@ -283,7 +392,7 @@ pub fn getResources(self: *Store, opts: Server.ResourceOptions) !std.ArrayList(R
if (resource.level > max_level) continue; if (resource.level > max_level) continue;
} }
if (opts.drop) |drop| { if (opts.drop) |drop| {
const item_id = try self.getItemId(drop); const item_id = try self.getCodeId(drop);
if (!DropRate.doesListContain(&resource.drops, item_id)) { if (!DropRate.doesListContain(&resource.drops, item_id)) {
continue; continue;
} }
@ -296,9 +405,18 @@ pub fn getResources(self: *Store, opts: Server.ResourceOptions) !std.ArrayList(R
} }
pub fn putResource(self: *Store, resource: Resource) !void { pub fn putResource(self: *Store, resource: Resource) !void {
var entry = try self.resources.getOrPut(resource.code); const code = self.getCode(resource.code_id).?;
var entry = try self.resources.getOrPut(code);
if (entry.found_existing) { if (entry.found_existing) {
entry.value_ptr.deinit(self.allocator); entry.value_ptr.deinit(self.allocator);
} }
entry.value_ptr.* = resource; entry.value_ptr.* = resource;
} }
fn clearResources(self: *Store) void {
var resourcesIter = self.resources.valueIterator();
while (resourcesIter.next()) |resource| {
resource.deinit(self.allocator);
}
self.resources.clearAndFree();
}

View File

@ -9,6 +9,11 @@ pub fn build(b: *std.Build) void {
var api: *Module = undefined; var api: *Module = undefined;
{ {
const s2s_dep = b.dependency("s2s", .{
.target = target,
.optimize = optimize,
});
api = b.createModule(.{ api = b.createModule(.{
.root_source_file = b.path("api/root.zig"), .root_source_file = b.path("api/root.zig"),
.target = target, .target = target,
@ -17,6 +22,7 @@ pub fn build(b: *std.Build) void {
}); });
api.addIncludePath(b.path("api/date_time")); api.addIncludePath(b.path("api/date_time"));
api.addCSourceFile(.{ .file = b.path("api/date_time/timegm.c") }); api.addCSourceFile(.{ .file = b.path("api/date_time/timegm.c") });
api.addImport("s2s", s2s_dep.module("s2s"));
} }
var lib: *Module = undefined; var lib: *Module = undefined;
@ -47,6 +53,7 @@ pub fn build(b: *std.Build) void {
.optimize = optimize, .optimize = optimize,
}); });
cli.root_module.addImport("artificer", lib); cli.root_module.addImport("artificer", lib);
cli.root_module.addImport("artifacts-api", api);
b.installArtifact(cli); b.installArtifact(cli);
const run_cmd = b.addRunArtifact(cli); const run_cmd = b.addRunArtifact(cli);

View File

@ -3,10 +3,14 @@
.version = "0.1.0", .version = "0.1.0",
.minimum_zig_version = "0.12.0", .minimum_zig_version = "0.12.0",
.dependencies = .{ .dependencies = .{
.@"raylib-zig" = .{ .@"raylib-zig" = .{
.url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz", .url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz",
.hash = "12204a223b19043e17b79300413d02f60fc8004c0d9629b8d8072831e352a78bf212" .hash = "12204a223b19043e17b79300413d02f60fc8004c0d9629b8d8072831e352a78bf212"
}, },
.s2s = .{
.url = "https://github.com/ziglibs/s2s/archive/b30205d5e9204899fb6d0fdf28d00ed4d18fe9c9.tar.gz",
.hash = "12202c39c98f05041f1052c268132669dbfcda87e4dbb0353cd84a6070924c8ac0e3",
},
}, },
.paths = .{ "" }, .paths = .{""},
} }

View File

@ -1,8 +1,10 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const Artificer = @import("artificer");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Artificer = @import("artificer");
const Api = @import("artifacts-api");
fn getAPITokenFromArgs(allocator: Allocator) !?[]u8 { fn getAPITokenFromArgs(allocator: Allocator) !?[]u8 {
const args = try std.process.argsAlloc(allocator); const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args); defer std.process.argsFree(allocator, args);
@ -30,11 +32,13 @@ pub fn main() !void {
var artificer = try Artificer.init(allocator, token); var artificer = try Artificer.init(allocator, token);
defer artificer.deinit(); defer artificer.deinit();
if (builtin.mode != .Debug) { const cache_path = try std.fs.cwd().realpathAlloc(allocator, "api-store.bin");
std.log.info("Prefetching server data", .{}); defer allocator.free(cache_path);
try artificer.server.prefetch();
}
std.log.info("Prefetching server data", .{});
try artificer.server.prefetchCached(cache_path);
if (false) {
std.log.info("Starting main loop", .{}); std.log.info("Starting main loop", .{});
while (true) { while (true) {
const waitUntil = artificer.nextStepAt(); const waitUntil = artificer.nextStepAt();
@ -45,4 +49,5 @@ pub fn main() !void {
try artificer.step(); try artificer.step();
} }
}
} }

View File

@ -66,6 +66,12 @@ pub fn main() anyerror!void {
var artificer = try Artificer.init(allocator, token); var artificer = try Artificer.init(allocator, token);
defer artificer.deinit(); defer artificer.deinit();
const cache_path = try std.fs.cwd().realpathAlloc(allocator, "api-store.bin");
defer allocator.free(cache_path);
std.log.info("Prefetching server data", .{});
try artificer.server.prefetchCached(cache_path);
rl.initWindow(800, 450, "Artificer"); rl.initWindow(800, 450, "Artificer");
defer rl.closeWindow(); defer rl.closeWindow();