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 Store = @import("store.zig");
pub const Character = @import("./schemas/character.zig");
pub const ServerStatus = @import("./schemas/status.zig");
pub const Map = @import("./schemas/map.zig");
pub const Position = @import("position.zig");
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 item_id = try store.getItemId(code_str);
const item_id = try store.getCodeId(code_str);
return DropRate{
.item_id = item_id,

View File

@ -38,9 +38,8 @@ pub const TypeUtils = EnumStringUtils(Type, .{
.{ "currency" , .currency },
});
allocator: Allocator,
name: []u8,
code: []u8,
code_id: Store.CodeId,
level: u64,
type: Type,
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");
return Item{
.allocator = allocator,
.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),
.type = TypeUtils.fromString(item_type_str) orelse return error.InvalidType,
.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 {
self.allocator.free(self.name);
self.allocator.free(self.code);
self.allocator.free(self.subtype);
self.allocator.free(self.description);
pub fn deinit(self: Item, allocator: Allocator) void {
allocator.free(self.name);
allocator.free(self.subtype);
allocator.free(self.description);
}

View File

@ -59,10 +59,10 @@ pub fn parse(store: *Store, obj: json.ObjectMap, allocator: Allocator) !Monster
}
return Monster{
.name = try json_utils.dupeStringRequired(allocator, obj, "name"),
.code = (try store.getCodeIdJson(allocator, obj, "code")) orelse return error.MissingProperty,
.level = @intCast(level),
.hp = @intCast(hp),
.name = try json_utils.dupeStringRequired(allocator, obj, "name"),
.code_id = (try store.getCodeIdJson(obj, "code")) orelse return error.MissingProperty,
.level = @intCast(level),
.hp = @intCast(hp),
.fire = try ElementalStats.parse(obj, "attack_fire" , "res_fire" ),
.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");
return Resource{
.name = try json_utils.dupeStringRequired(allocator, obj, "name"),
.code = (try store.getCodeIdJson(allocator, obj, "code")) orelse return error.MissingProperty,
.level = @intCast(level),
.skill = SkillUtils.fromString(skill_str) orelse return error.InvalidSkill,
.drops = try DropRate.parseList(store, drops_array)
.name = try json_utils.dupeStringRequired(allocator, obj, "name"),
.code_id = (try store.getCodeIdJson(obj, "code")) orelse return error.MissingProperty,
.level = @intCast(level),
.skill = SkillUtils.fromString(skill_str) orelse return error.InvalidSkill,
.drops = try DropRate.parseList(store, drops_array)
};
}

View File

@ -407,6 +407,26 @@ pub fn prefetch(self: *Server) !void {
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 ------------------------
pub fn getServerStatus(self: *Server) FetchError!ServerStatus {

View File

@ -1,6 +1,7 @@
const std = @import("std");
const json_utils = @import("json_utils.zig");
const Server = @import("./server.zig");
const s2s = @import("s2s");
const json = std.json;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
@ -17,61 +18,48 @@ const DropRate = @import("./schemas/drop_rate.zig");
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,
codes: std.ArrayList([]u8),
characters: std.ArrayList(Character),
items: std.StringHashMap(Item),
maps: std.AutoHashMap(Position, Map),
resources: std.StringHashMap(Resource),
monsters: std.StringHashMap(Monster),
characters: Characters,
items: ItemsMap,
maps: MapsMap,
resources: ResourcesMap,
monsters: MonstersMap,
// TODO: bank
pub fn init(allocator: Allocator) Store {
return Store{
.allocator = allocator,
.codes = std.ArrayList([]u8).init(allocator),
.characters = std.ArrayList(Character).init(allocator),
.items = std.StringHashMap(Item).init(allocator),
.maps = std.AutoHashMap(Position, Map).init(allocator),
.resources = std.StringHashMap(Resource).init(allocator),
.monsters = std.StringHashMap(Monster).init(allocator),
.characters = Characters.init(allocator),
.items = ItemsMap.init(allocator),
.maps = MapsMap.init(allocator),
.resources = ResourcesMap.init(allocator),
.monsters = MonstersMap.init(allocator),
};
}
pub fn deinit(self: *Store) void {
for (self.codes.items) |code| {
self.allocator.free(code);
}
self.codes.deinit();
self.clearCodes();
for (self.characters.items) |*char| {
char.deinit();
}
self.characters.deinit();
var itemsIter = self.items.valueIterator();
while (itemsIter.next()) |item| {
item.deinit();
}
self.items.deinit();
self.clearItems();
var mapsIter = self.maps.valueIterator();
while (mapsIter.next()) |map| {
map.deinit(self.allocator);
}
self.maps.deinit();
self.clearMaps();
var resourcesIter = self.resources.valueIterator();
while (resourcesIter.next()) |resource| {
resource.deinit(self.allocator);
}
self.resources.deinit();
self.clearResources();
var monstersIter = self.monsters.valueIterator();
while (monstersIter.next()) |monster| {
monster.deinit(self.allocator);
}
self.monsters.deinit();
self.clearMonsters();
}
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);
}
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 ------------------------------
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;
}
fn clearMaps(self: *Store) void {
var mapsIter = self.maps.valueIterator();
while (mapsIter.next()) |map| {
map.deinit(self.allocator);
}
self.maps.clearAndFree();
}
// ----------------------- 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;
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);
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 {
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) {
entry.value_ptr.deinit();
entry.value_ptr.deinit(self.allocator);
}
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 ------------------------------
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 (opts.drop) |drop| {
const item_id = try self.getItemId(drop);
const item_id = try self.getCodeId(drop);
if (!DropRate.doesListContain(&monster.drops, item_id)) {
continue;
}
@ -257,13 +357,22 @@ pub fn getMonsters(self: *Store, opts: Server.MonsterOptions) !std.ArrayList(Mon
}
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) {
entry.value_ptr.deinit(self.allocator);
}
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 ------------------------------
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 (opts.drop) |drop| {
const item_id = try self.getItemId(drop);
const item_id = try self.getCodeId(drop);
if (!DropRate.doesListContain(&resource.drops, item_id)) {
continue;
}
@ -296,9 +405,18 @@ pub fn getResources(self: *Store, opts: Server.ResourceOptions) !std.ArrayList(R
}
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) {
entry.value_ptr.deinit(self.allocator);
}
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;
{
const s2s_dep = b.dependency("s2s", .{
.target = target,
.optimize = optimize,
});
api = b.createModule(.{
.root_source_file = b.path("api/root.zig"),
.target = target,
@ -17,6 +22,7 @@ pub fn build(b: *std.Build) void {
});
api.addIncludePath(b.path("api/date_time"));
api.addCSourceFile(.{ .file = b.path("api/date_time/timegm.c") });
api.addImport("s2s", s2s_dep.module("s2s"));
}
var lib: *Module = undefined;
@ -47,6 +53,7 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});
cli.root_module.addImport("artificer", lib);
cli.root_module.addImport("artifacts-api", api);
b.installArtifact(cli);
const run_cmd = b.addRunArtifact(cli);

View File

@ -3,10 +3,14 @@
.version = "0.1.0",
.minimum_zig_version = "0.12.0",
.dependencies = .{
.@"raylib-zig" = .{
.url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz",
.hash = "12204a223b19043e17b79300413d02f60fc8004c0d9629b8d8072831e352a78bf212"
},
.@"raylib-zig" = .{
.url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz",
.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 builtin = @import("builtin");
const Artificer = @import("artificer");
const Allocator = std.mem.Allocator;
const Artificer = @import("artificer");
const Api = @import("artifacts-api");
fn getAPITokenFromArgs(allocator: Allocator) !?[]u8 {
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
@ -30,11 +32,13 @@ pub fn main() !void {
var artificer = try Artificer.init(allocator, token);
defer artificer.deinit();
if (builtin.mode != .Debug) {
std.log.info("Prefetching server data", .{});
try artificer.server.prefetch();
}
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);
if (false) {
std.log.info("Starting main loop", .{});
while (true) {
const waitUntil = artificer.nextStepAt();
@ -45,4 +49,5 @@ pub fn main() !void {
try artificer.step();
}
}
}

View File

@ -66,6 +66,12 @@ pub fn main() anyerror!void {
var artificer = try Artificer.init(allocator, token);
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");
defer rl.closeWindow();