From ceaea86169df1c76c7239226b46ee2d7020e2a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ki=C3=ABd=20Llaentenn?= Date: Sun, 8 Oct 2023 15:44:20 -0400 Subject: [PATCH] feat: (WIP) serialization --- src/alert.zig | 31 +++++------ src/buffer.zig | 5 ++ src/combat.zig | 2 +- src/err.zig | 5 +- src/events.zig | 20 +++---- src/list.zig | 11 ++++ src/main.zig | 51 ++++++++++-------- src/mapgen.zig | 7 ++- src/rng.zig | 7 +-- src/scores.zig | 42 +++++++-------- src/serializer.zig | 73 +++++++++++++++++++++++++ src/state.zig | 130 ++++++++++++++++++++++++--------------------- 12 files changed, 238 insertions(+), 146 deletions(-) create mode 100644 src/serializer.zig diff --git a/src/alert.zig b/src/alert.zig index 94e9e034..1347ef13 100644 --- a/src/alert.zig +++ b/src/alert.zig @@ -102,21 +102,18 @@ pub const ThreatResponse = struct { pub const AList = std.ArrayList(@This()); }; -pub var threats: std.AutoHashMap(Threat, ThreatData) = undefined; -pub var responses: ThreatResponse.AList = undefined; - pub fn init() void { - threats = @TypeOf(threats).init(state.GPA.allocator()); - responses = @TypeOf(responses).init(state.GPA.allocator()); + state.threats = @TypeOf(state.threats).init(state.GPA.allocator()); + state.responses = @TypeOf(state.responses).init(state.GPA.allocator()); } pub fn deinit() void { - threats.clearAndFree(); - responses.deinit(); + state.threats.clearAndFree(); + state.responses.deinit(); } pub fn getThreat(threat: Threat) *ThreatData { - return (threats.getOrPutValue(threat, .{}) catch err.wat()).value_ptr; + return (state.threats.getOrPutValue(threat, .{}) catch err.wat()).value_ptr; } pub fn reportThreat(by: ?*Mob, threat: Threat, threattype: ThreatIncrease) void { @@ -142,7 +139,7 @@ pub fn reportThreat(by: ?*Mob, threat: Threat, threattype: ThreatIncrease) void getThreat(.Unknown).is_active = true; } - // Don't report threats if the guy is dead + // Don't report state.threats if the guy is dead if (threat == .Specific and threat.Specific.is_dead and threat.Specific.corpse_info.is_noticed) { @@ -174,24 +171,24 @@ pub fn dismissThreat(by: ?*Mob, threat: Threat) void { // Also there would need to be checks in place for when threats are // dismissed redundantly // - //threats.put(threat, getThreat(.General).level - getThreat(threat).level); + //state.threats.put(threat, getThreat(.General).level - getThreat(threat).level); assert(threat != .General); assert(by == null or by.?.faction == .Necromancer); _ = by; - _ = threats.remove(threat); + _ = state.threats.remove(threat); } pub fn queueThreatResponse(response: ThreatResponseType) void { - responses.append(.{ .type = response }) catch err.wat(); + state.responses.append(.{ .type = response }) catch err.wat(); } pub fn tickThreats(level: usize) void { // Unsure if modifying container while iterator() is active is safe to do var dismiss_threats = StackBuffer(Threat, 64).init(null); - var iter = threats.iterator(); + var iter = state.threats.iterator(); while (iter.next()) |entry| { if (entry.key_ptr.* == .General) continue; @@ -225,8 +222,8 @@ pub fn tickThreats(level: usize) void { for (dismiss_threats.constSlice()) |threat| dismissThreat(null, threat); - if (responses.items.len > 0) { - const response = responses.pop(); + if (state.responses.items.len > 0) { + const response = state.responses.pop(); switch (response.type) { .ReinforceAgainstEnemy => |r| { const mob_template = switch (r.reinforcement) { @@ -259,7 +256,7 @@ pub fn tickThreats(level: usize) void { } else |_| { // No space near stairs. Add the response back, wait until next // time, hopefully the traffic dissipates. - responses.append(response) catch err.wat(); + state.responses.append(response) catch err.wat(); } }, .ReinforceRoom => |r| { @@ -292,7 +289,7 @@ pub fn tickThreats(level: usize) void { if (mobs.placeMobNearStairs(mob_template, level, opts)) |_| {} else |_| { // No space near stairs. Add the response back, wait until next // time, hopefully the traffic dissipates. - responses.append(response) catch err.wat(); + state.responses.append(response) catch err.wat(); } } }, diff --git a/src/buffer.zig b/src/buffer.zig index 45daaa89..54799e89 100644 --- a/src/buffer.zig +++ b/src/buffer.zig @@ -6,6 +6,7 @@ const mem = std.mem; const assert = std.debug.assert; const rng = @import("rng.zig"); +const serializer = @import("serializer.zig"); pub const StringBuf64 = StackBuffer(u8, 64); @@ -137,6 +138,10 @@ pub fn StackBuffer(comptime T: type, comptime capacity: usize) type { pub fn jsonStringify(val: @This(), opts: std.json.StringifyOptions, stream: anytype) !void { try std.json.stringify(val.constSlice(), opts, stream); } + + pub fn serialize(val: @This(), out: anytype) !void { + try serializer.serialize([]const T, val.constSlice(), out); + } }; } diff --git a/src/combat.zig b/src/combat.zig index 74ce411f..3d13bcc1 100644 --- a/src/combat.zig +++ b/src/combat.zig @@ -371,7 +371,7 @@ pub fn disruptIndividualUndead(mob: *Mob) void { } test { - rng.seed = 2384928349; + state.seed = 2384928349; rng.init(); var i: usize = 10; diff --git a/src/err.zig b/src/err.zig index 29ab1260..53fca942 100644 --- a/src/err.zig +++ b/src/err.zig @@ -6,7 +6,6 @@ const std = @import("std"); const ui = @import("ui.zig"); const state = @import("state.zig"); -const rng = @import("rng.zig"); const sentry = @import("sentry.zig"); pub fn ensure(expr: bool, comptime err_message: []const u8, args: anytype) !void { @@ -20,7 +19,7 @@ pub fn bug(comptime fmt: []const u8, args: anytype) noreturn { @setCold(true); ui.deinit() catch {}; - std.log.err("Fatal bug encountered. (Seed: {})", .{rng.seed}); + std.log.err("Fatal bug encountered. (Seed: {})", .{state.seed}); std.log.err("BUG: " ++ fmt, args); if (!state.sentry_disabled) { @@ -35,7 +34,7 @@ pub fn bug(comptime fmt: []const u8, args: anytype) noreturn { std.fmt.allocPrint(alloc, fmt, args) catch unreachable, &[_]sentry.SentryEvent.TagSet.Tag{.{ .name = "seed", - .value = std.fmt.allocPrint(alloc, "{}", .{rng.seed}) catch unreachable, + .value = std.fmt.allocPrint(alloc, "{}", .{state.seed}) catch unreachable, }}, @errorReturnTrace(), @returnAddress(), diff --git a/src/events.zig b/src/events.zig index bd0c1c07..be9060c8 100644 --- a/src/events.zig +++ b/src/events.zig @@ -81,23 +81,19 @@ pub const EVENTS = [_]struct { p: usize, v: *const Event }{ .{ .p = 75, .v = &EV_SHIELD_DISALLOW }, }; -pub var completed_events: Event.AList = undefined; - pub fn init() void { - completed_events = @TypeOf(completed_events).init(state.GPA.allocator()); + // Nothing } pub fn deinit() void { - completed_events.deinit(); + // Nothing } pub fn eventUsedCount(id: []const u8) usize { - var i: usize = 0; - for (completed_events.items) |completed| { - if (mem.eql(u8, id, completed.id)) - i += 1; - } - return i; + const ind = for (EVENTS) |ev, i| { + if (mem.eql(u8, ev.v.id, id)) break i; + } else err.wat(); + return state.completed_events[ind]; } pub fn eventCanBeUsed(event: *const Event) bool { @@ -115,12 +111,12 @@ pub fn eventCanBeUsed(event: *const Event) bool { // XXX: Need to add checks for restrictions etc when that's added // pub fn executeGlobalEvents() void { - for (&EVENTS) |event| { + for (&EVENTS) |event, i| { if (rng.percent(event.p) and eventCanBeUsed(event.v)) { var new_event = event.v.*; for (new_event.effect) |effect| effect.apply() catch unreachable; - completed_events.append(new_event) catch err.wat(); + state.completed_events[i] += 1; } } } diff --git a/src/list.zig b/src/list.zig index 5d047766..fdbb899e 100644 --- a/src/list.zig +++ b/src/list.zig @@ -3,6 +3,8 @@ const mem = std.mem; const assert = std.debug.assert; const testing = std.testing; +const serializer = @import("serializer.zig"); + // Basic node that can be used for scalar data. pub fn ScalarNode(comptime T: type) type { return struct { @@ -138,6 +140,15 @@ pub fn LinkedList(comptime T: type) type { pub fn iteratorReverse(self: *const Self) Iterator { return Iterator{ .current = self.tail, .reverse = true }; } + + pub fn serialize(val: @This(), out: anytype) !void { + var iter = val.iterator(); + var i: usize = 0; + while (iter.next()) |_| i += 1; + try serializer.serialize(usize, i, out); + iter = val.iterator(); + while (iter.next()) |item| try serializer.serialize(T, item, out); + } }; } diff --git a/src/main.zig b/src/main.zig index 5e36f70c..44f9a865 100644 --- a/src/main.zig +++ b/src/main.zig @@ -13,29 +13,30 @@ const StackBuffer = @import("buffer.zig").StackBuffer; const ai = @import("ai.zig"); const alert = @import("alert.zig"); -const rng = @import("rng.zig"); -const janet = @import("janet.zig"); -const player = @import("player.zig"); -const font = @import("font.zig"); +const display = @import("display.zig"); +const err = @import("err.zig"); const events = @import("events.zig"); -const literature = @import("literature.zig"); const explosions = @import("explosions.zig"); -const tasks = @import("tasks.zig"); const fire = @import("fire.zig"); -const items = @import("items.zig"); -const utils = @import("utils.zig"); +const font = @import("font.zig"); const gas = @import("gas.zig"); +const items = @import("items.zig"); +const janet = @import("janet.zig"); +const literature = @import("literature.zig"); const mapgen = @import("mapgen.zig"); const mobs = @import("mobs.zig"); +const player = @import("player.zig"); +const rng = @import("rng.zig"); +const scores = @import("scores.zig"); +const sentry = @import("sentry.zig"); +const serializer = @import("serializer.zig"); +const state = @import("state.zig"); const surfaces = @import("surfaces.zig"); -const ui = @import("ui.zig"); +const tasks = @import("tasks.zig"); const termbox = @import("termbox.zig"); -const display = @import("display.zig"); const types = @import("types.zig"); -const sentry = @import("sentry.zig"); -const state = @import("state.zig"); -const err = @import("err.zig"); -const scores = @import("scores.zig"); +const ui = @import("ui.zig"); +const utils = @import("utils.zig"); const Direction = types.Direction; const Coord = types.Coord; @@ -84,7 +85,7 @@ pub fn panic(msg: []const u8, trace: ?*std.builtin.StackTrace) noreturn { 0 => { __panic_stage = 1; ui.deinit() catch {}; - std.log.err("Fatal error encountered. (Seed: {})", .{rng.seed}); + std.log.err("Fatal error encountered. (Seed: {})", .{state.seed}); if (!state.sentry_disabled) { var membuf: [65535]u8 = undefined; @@ -98,7 +99,7 @@ pub fn panic(msg: []const u8, trace: ?*std.builtin.StackTrace) noreturn { msg, &[_]sentry.SentryEvent.TagSet.Tag{.{ .name = "seed", - .value = std.fmt.allocPrint(alloc, "{}", .{rng.seed}) catch unreachable, + .value = std.fmt.allocPrint(alloc, "{}", .{state.seed}) catch unreachable, }}, trace, @returnAddress(), @@ -160,7 +161,7 @@ fn initGameState() void { state.dungeon.* = types.Dungeon{}; rng.init(); - for (mapgen.floor_seeds) |*seed| + for (state.floor_seeds) |*seed| seed.* = rng.int(u64); for (state.default_patterns) |*r| r.pattern_checker.reset(); @@ -493,6 +494,10 @@ fn readInput() !bool { // ui.hud_win.deinit(); // ui.hud_win.init(); // ui.map_win.drawTextLinef("This is a test.", .{}, .{}); + var buf = std.ArrayList(u8).init(state.GPA.allocator()); + defer buf.deinit(); + serializer.serialize(Mob, state.player.*, buf.writer()) catch err.wat(); + std.fs.cwd().writeFile("dump.dat", buf.items) catch err.wat(); }, .F8 => { _ = janet.loadFile("scripts/particles.janet", state.GPA.allocator()) catch continue; @@ -1158,7 +1163,7 @@ fn testerMain() void { fn profilerMain() void { // const LEVEL = 0; - std.log.info("[ Seed: {} ]", .{rng.seed}); + std.log.info("[ Seed: {} ]", .{state.seed}); state.sentry_disabled = true; assert(initGame(true, 0)); @@ -1230,8 +1235,8 @@ fn analyzerMain() void { var i: usize = 0; while (i < ITERS) : (i += 1) { - rng.seed = @intCast(u64, std.time.milliTimestamp()); - std.log.info("*** \x1b[94;1m ITERATION \x1b[m {} (seed: {})", .{ i, rng.seed }); + state.seed = @intCast(u64, std.time.milliTimestamp()); + std.log.info("*** \x1b[94;1m ITERATION \x1b[m {} (seed: {})", .{ i, state.seed }); initGameState(); const S = state.PLAYER_STARTING_LEVEL; @@ -1266,12 +1271,12 @@ pub fn actualMain() anyerror!void { if (std.process.getEnvVarOwned(state.GPA.allocator(), "RL_SEED")) |seed_str| { defer state.GPA.allocator().free(seed_str); - rng.seed = std.fmt.parseInt(u64, seed_str, 0) catch |e| b: { + state.seed = std.fmt.parseInt(u64, seed_str, 0) catch |e| b: { std.log.err("Could not parse RL_SEED (reason: {}); using default.", .{e}); break :b 0; }; } else |_| { - rng.seed = @intCast(u64, std.time.milliTimestamp()); + state.seed = @intCast(u64, std.time.milliTimestamp()); } if (std.process.getEnvVarOwned(state.GPA.allocator(), "RL_MODE")) |v| { @@ -1388,7 +1393,7 @@ pub fn main() void { "propagated error trace", &[_]sentry.SentryEvent.TagSet.Tag{.{ .name = "seed", - .value = std.fmt.allocPrint(alloc, "{}", .{rng.seed}) catch unreachable, + .value = std.fmt.allocPrint(alloc, "{}", .{state.seed}) catch unreachable, }}, error_trace, null, diff --git a/src/mapgen.zig b/src/mapgen.zig index 5a90c008..62bc5430 100644 --- a/src/mapgen.zig +++ b/src/mapgen.zig @@ -146,7 +146,6 @@ const Range = struct { from: Coord, to: Coord }; pub var s_fabs: PrefabArrayList = undefined; pub var n_fabs: PrefabArrayList = undefined; pub var fab_records: std.StringHashMap(Prefab.PlacementRecord) = undefined; -pub var floor_seeds: [LEVELS]u64 = undefined; const gif = @import("build_options").tunneler_gif; const giflib = if (gif) @cImport(@cInclude("gif_lib.h")) else null; @@ -3399,7 +3398,7 @@ pub fn initLevelTest(prefab: []const u8, entry: bool) !void { } pub fn initLevel(level: usize) void { - rng.useTemp(floor_seeds[level]); + rng.useTemp(state.floor_seeds[level]); var tries: usize = 0; while (true) { @@ -3481,8 +3480,8 @@ pub const LevelAnalysis = struct { .prefabs = std.ArrayList(Pair).init(alloc), .items = std.ArrayList(Pair2).init(alloc), .mobs = std.ArrayList(Pair).init(alloc), - .seed = rng.seed, - .floor_seed = floor_seeds[z], + .seed = state.seed, + .floor_seed = state.floor_seeds[z], }; } diff --git a/src/rng.zig b/src/rng.zig index d4fafff9..617e7dbf 100644 --- a/src/rng.zig +++ b/src/rng.zig @@ -9,11 +9,12 @@ const math = std.math; var rng: rand.Isaac64 = undefined; var rng2: rand.Isaac64 = undefined; pub var using_temp = false; -pub var seed: u64 = undefined; +pub const seed = &@import("state.zig").seed; + //seed = 0xdefaced_cafe; pub fn init() void { - rng = rand.Isaac64.init(seed); + rng = rand.Isaac64.init(seed.*); } pub fn useTemp(tseed: u64) void { @@ -134,7 +135,7 @@ pub fn choose2(comptime T: type, arr: []const T, comptime weight_field: []const test "range" { const testing = std.testing; - seed = 2384928349; + seed.* = 2384928349; init(); var i: usize = 0; diff --git a/src/scores.zig b/src/scores.zig index 56580425..1542443f 100644 --- a/src/scores.zig +++ b/src/scores.zig @@ -60,7 +60,7 @@ pub const Info = struct { // FIXME: should be a cleaner way to do this... var s: Self = undefined; - s.seed = rng.seed; + s.seed = state.seed; if (std.process.getEnvVarOwned(state.GPA.allocator(), "USER")) |env| { s.username.reinit(env); @@ -340,17 +340,15 @@ pub const StatValue = struct { }; }; -pub var data = std.enums.directEnumArray(Stat, StatValue, 0, undefined); - pub fn init() void { - for (data) |*entry, i| + for (state.scoredata) |*entry, i| if (std.meta.intToEnum(Stat, i)) |_| { entry.* = .{}; } else |_| {}; } pub fn get(s: Stat) *StatValue { - return &data[@enumToInt(s)]; + return &state.scoredata[@enumToInt(s)]; } // XXX: this hidden reliance on state.player.z could cause bugs @@ -358,8 +356,8 @@ pub fn get(s: Stat) *StatValue { pub fn recordUsize(stat: Stat, value: usize) void { switch (stat.stattype()) { .SingleUsize => { - data[@enumToInt(stat)].SingleUsize.total += value; - data[@enumToInt(stat)].SingleUsize.each[state.player.coord.z] += value; + state.scoredata[@enumToInt(stat)].SingleUsize.total += value; + state.scoredata[@enumToInt(stat)].SingleUsize.each[state.player.coord.z] += value; }, else => unreachable, } @@ -385,18 +383,18 @@ pub fn recordTaggedUsize(stat: Stat, tag: Tag, value: usize) void { const key = tag.intoString(); switch (stat.stattype()) { .BatchUsize => { - data[@enumToInt(stat)].BatchUsize.total += value; - const index: ?usize = for (data[@enumToInt(stat)].BatchUsize.singles.constSlice()) |single, i| { + state.scoredata[@enumToInt(stat)].BatchUsize.total += value; + const index: ?usize = for (state.scoredata[@enumToInt(stat)].BatchUsize.singles.constSlice()) |single, i| { if (mem.eql(u8, single.id.constSlice(), key.constSlice())) break i; } else null; if (index) |i| { - data[@enumToInt(stat)].BatchUsize.singles.slice()[i].val.total += value; - data[@enumToInt(stat)].BatchUsize.singles.slice()[i].val.each[state.player.coord.z] += value; + state.scoredata[@enumToInt(stat)].BatchUsize.singles.slice()[i].val.total += value; + state.scoredata[@enumToInt(stat)].BatchUsize.singles.slice()[i].val.each[state.player.coord.z] += value; } else { - data[@enumToInt(stat)].BatchUsize.singles.append(.{}) catch err.wat(); - data[@enumToInt(stat)].BatchUsize.singles.lastPtr().?.id = key; - data[@enumToInt(stat)].BatchUsize.singles.lastPtr().?.val.total += value; - data[@enumToInt(stat)].BatchUsize.singles.lastPtr().?.val.each[state.player.coord.z] += value; + state.scoredata[@enumToInt(stat)].BatchUsize.singles.append(.{}) catch err.wat(); + state.scoredata[@enumToInt(stat)].BatchUsize.singles.lastPtr().?.id = key; + state.scoredata[@enumToInt(stat)].BatchUsize.singles.lastPtr().?.val.total += value; + state.scoredata[@enumToInt(stat)].BatchUsize.singles.lastPtr().?.val.each[state.player.coord.z] += value; } }, else => unreachable, @@ -404,7 +402,7 @@ pub fn recordTaggedUsize(stat: Stat, tag: Tag, value: usize) void { } fn _isLevelSignificant(level: usize) bool { - return data[@enumToInt(@as(Stat, .TurnsSpent))].SingleUsize.each[level] > 0; + return state.scoredata[@enumToInt(@as(Stat, .TurnsSpent))].SingleUsize.each[level] > 0; } fn exportTextMorgue(info: Info, alloc: mem.Allocator) !std.ArrayList(u8) { @@ -465,8 +463,8 @@ fn exportTextMorgue(info: Info, alloc: mem.Allocator) !std.ArrayList(u8) { try w.print("\n", .{}); } - const killed = data[@enumToInt(@as(Stat, .KillRecord))].BatchUsize.total; - const stabbed = data[@enumToInt(@as(Stat, .StabRecord))].BatchUsize.total; + const killed = state.scoredata[@enumToInt(@as(Stat, .KillRecord))].BatchUsize.total; + const stabbed = state.scoredata[@enumToInt(@as(Stat, .StabRecord))].BatchUsize.total; try w.print("You killed {} foe(s), stabbing {} of them.\n", .{ killed, stabbed }); try w.print("\n", .{}); @@ -574,7 +572,7 @@ fn exportTextMorgue(info: Info, alloc: mem.Allocator) !std.ArrayList(u8) { try w.print("\n", .{}); }, .Stat => |stat| { - const entry = &data[@enumToInt(stat.s)]; + const entry = &state.scoredata[@enumToInt(stat.s)]; switch (stat.s.stattype()) { .SingleUsize => { try w.print("{s: <24} {: >5} | ", .{ stat.n, entry.SingleUsize.total }); @@ -628,7 +626,7 @@ fn exportJsonMorgue(info: Info) !std.ArrayList(u8) { for (&CHUNKS) |chunk, chunk_i| switch (chunk) { .Header => {}, .Stat => |stat| { - const entry = &data[@enumToInt(stat.s)]; + const entry = &state.scoredata[@enumToInt(stat.s)]; try w.print("\"{s}\": {{", .{stat.n}); try w.print("\"type\": \"{s}\",", .{@tagName(stat.s.stattype())}); switch (stat.s.stattype()) { @@ -680,7 +678,7 @@ pub fn createMorgue() Info { const morgue = exportJsonMorgue(info) catch err.wat(); defer morgue.deinit(); - const filename = std.fmt.allocPrintZ(state.GPA.allocator(), "morgue-{s}-{}-{}-{:0>2}-{:0>2}-{}:{}.json", .{ info.username.constSlice(), rng.seed, info.end_datetime.Y, info.end_datetime.M, info.end_datetime.D, info.end_datetime.h, info.end_datetime.m }) catch err.oom(); + const filename = std.fmt.allocPrintZ(state.GPA.allocator(), "morgue-{s}-{}-{}-{:0>2}-{:0>2}-{}:{}.json", .{ info.username.constSlice(), state.seed, info.end_datetime.Y, info.end_datetime.M, info.end_datetime.D, info.end_datetime.h, info.end_datetime.m }) catch err.oom(); defer state.GPA.allocator().free(filename); (std.fs.cwd().openDir("morgue", .{}) catch err.wat()).writeFile(filename, morgue.items[0..]) catch |e| { @@ -694,7 +692,7 @@ pub fn createMorgue() Info { const morgue = exportTextMorgue(info, state.GPA.allocator()) catch err.wat(); defer morgue.deinit(); - const filename = std.fmt.allocPrintZ(state.GPA.allocator(), "morgue-{s}-{}-{}-{:0>2}-{:0>2}-{}:{}.txt", .{ info.username.constSlice(), rng.seed, info.end_datetime.Y, info.end_datetime.M, info.end_datetime.D, info.end_datetime.h, info.end_datetime.m }) catch err.oom(); + const filename = std.fmt.allocPrintZ(state.GPA.allocator(), "morgue-{s}-{}-{}-{:0>2}-{:0>2}-{}:{}.txt", .{ info.username.constSlice(), state.seed, info.end_datetime.Y, info.end_datetime.M, info.end_datetime.D, info.end_datetime.h, info.end_datetime.m }) catch err.oom(); defer state.GPA.allocator().free(filename); (std.fs.cwd().openDir("morgue", .{}) catch err.wat()).writeFile(filename, morgue.items[0..]) catch |e| { diff --git a/src/serializer.zig b/src/serializer.zig new file mode 100644 index 00000000..633990d0 --- /dev/null +++ b/src/serializer.zig @@ -0,0 +1,73 @@ +const std = @import("std"); +const meta = std.meta; +const mem = std.mem; + +const types = @import("types.zig"); + +pub fn serialize(comptime T: type, obj: T, out: anytype) !void { + if (comptime mem.startsWith(u8, @typeName(T), "std.hash_map.HashMap(")) { + try serialize(usize, obj.count(), out); + var iter = obj.iterator(); + while (iter.next()) |entry| { + try serialize(@TypeOf(entry.key_ptr.*), entry.key_ptr.*, out); + try serialize(@TypeOf(entry.value_ptr.*), entry.value_ptr.*, out); + } + return; + } else if (comptime mem.startsWith(u8, @typeName(T), "std.array_list.ArrayList(")) { + try serialize(@TypeOf(obj.items), obj.items, out); + return; + } + + switch (@typeInfo(T)) { + .Bool => try out.writeIntLittle(u1, if (obj) @as(u1, 1) else 0), + .Int => try out.writeIntLittle(T, obj), + .Float => { + const F = if (T == f32) u32 else u64; + try out.writeIntLittle(F, @bitCast(F, obj)); + }, + .Pointer => |p| switch (p.size) { + .One => { + std.log.warn("Refusing to serialize pointer to {}", .{p.child}); + try serialize(usize, @ptrToInt(obj), out); + }, + .Slice => { + try serialize(usize, obj.len, out); + for (obj) |value| try serialize(p.child, value, out); + }, + .Many, .C => @compileError("Cannot serialize " ++ @typeName(T)), + }, + .Array => |a| { + try serialize(usize, a.len, out); + for (obj) |value| try serialize(a.child, value, out); + }, + .Struct => |info| { + if (comptime std.meta.trait.hasFn("serialize")(T)) { + try obj.serialize(out); + } else inline for (info.fields) |field| { + std.log.debug("Serializing {s: <20} ({s})", .{ field.name, @typeName(field.field_type) }); + const f = @field(obj, field.name); + try serialize(field.field_type, f, out); + } + }, + .Optional => |o| { + try serialize(u1, if (obj == null) @as(u1, 0) else 1, out); + try serialize(o.child, if (obj == null) undefined else obj.?, out); + }, + .Enum => |e| try serialize(e.tag_type, @enumToInt(obj), out), + .Union => |u| { + try serialize(meta.Tag(T), meta.activeTag(obj), out); + inline for (u.fields) |ufield| + if (mem.eql(u8, ufield.name, @tagName(meta.activeTag(obj)))) { + if (ufield.field_type != void) + try serialize(ufield.field_type, @field(obj, ufield.name), out); + break; + }; + }, + .Fn => {}, + else => @compileError("Cannot serialize " ++ @typeName(T)), + } +} + +test { + std.log.warn("{}", .{@sizeOf(u64)}); +} diff --git a/src/state.zig b/src/state.zig index 4fb3cd1a..ac91ab68 100644 --- a/src/state.zig +++ b/src/state.zig @@ -5,23 +5,26 @@ const assert = std.debug.assert; const enums = std.enums; const ai = @import("ai.zig"); +const alert = @import("alert.zig"); const astar = @import("astar.zig"); -const err = @import("err.zig"); -const player_m = @import("player.zig"); -const ui = @import("ui.zig"); -const display = @import("display.zig"); const dijkstra = @import("dijkstra.zig"); -const mapgen = @import("mapgen.zig"); -const mobs_m = @import("mobs.zig"); +const display = @import("display.zig"); +const events = @import("events.zig"); +const err = @import("err.zig"); const fire = @import("fire.zig"); -const items = @import("items.zig"); -const utils = @import("utils.zig"); +const fov = @import("fov.zig"); const gas = @import("gas.zig"); -const rng = @import("rng.zig"); +const items = @import("items.zig"); const literature = @import("literature.zig"); -const fov = @import("fov.zig"); -const types = @import("types.zig"); +const mapgen = @import("mapgen.zig"); +const mobs_m = @import("mobs.zig"); +const player_m = @import("player.zig"); +const rng = @import("rng.zig"); +const scores = @import("scores.zig"); const tsv = @import("tsv.zig"); +const types = @import("types.zig"); +const ui = @import("ui.zig"); +const utils = @import("utils.zig"); const Squad = types.Squad; const Mob = types.Mob; @@ -80,54 +83,14 @@ pub var GPA = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 10, }){}; -pub const mapgeometry = Coord.new2(LEVELS, WIDTH, HEIGHT); -pub var dungeon: *Dungeon = undefined; -pub var layout: [LEVELS][HEIGHT][WIDTH]Layout = [1][HEIGHT][WIDTH]Layout{[1][WIDTH]Layout{[1]Layout{.Unknown} ** WIDTH} ** HEIGHT} ** LEVELS; -pub var state: GameState = .Game; -pub var current_level: usize = PLAYER_STARTING_LEVEL; -pub var player: *Mob = undefined; -pub var player_inited = false; - -// zig fmt: off -pub var night_rep = [types.Faction.TOTAL]isize{ - // - // NEC @ CG YSM NC - 0, 0, 0, -10, 10, - // -}; -// zig fmt: on - pub var sentry_disabled = false; pub var log_disabled = false; +pub const mapgeometry = Coord.new2(LEVELS, WIDTH, HEIGHT); pub fn mapRect(level: usize) Rect { return Rect{ .start = Coord.new2(level, 0, 0), .width = WIDTH, .height = HEIGHT }; } -// XXX: []u8 instead of '[]const u8` because of tsv parsing limits -pub const LevelInfo = struct { - id: []u8, - depth: usize, - shortname: []u8, - name: []u8, - upgr: bool, - optional: bool, - stairs: [Dungeon.MAX_STAIRS]?[]u8, -}; -// Loaded at runtime from data/levelinfo.tsv -pub var levelinfo: [LEVELS]LevelInfo = undefined; - -pub const StatusStringInfo = struct { - name: []const u8, - unliving_name: ?[]const u8, - mini_name: ?[]const u8, -}; -pub var status_str_infos: std.enums.EnumArray(Status, ?StatusStringInfo) = - std.enums.EnumArray(Status, ?StatusStringInfo).initFill(null); - -pub var player_upgrades: [3]player_m.PlayerUpgradeInfo = undefined; -pub var player_conj_augments: [player_m.ConjAugment.TOTAL]player_m.ConjAugmentInfo = undefined; - // Cached return value of player.isPlayerSpotted() pub var player_is_spotted: struct { is_spotted: bool, @@ -137,6 +100,7 @@ pub var player_is_spotted: struct { // Unused now pub var default_patterns = [_]types.Ring{}; +// Data objects pub const MemoryTile = struct { tile: display.Cell, type: Type = .Immediate, @@ -144,22 +108,13 @@ pub const MemoryTile = struct { pub const Type = enum { Immediate, Echolocated, DetectUndead }; }; pub const MemoryTileMap = std.AutoHashMap(Coord, MemoryTile); - pub var memory: MemoryTileMap = undefined; -pub var descriptions: std.StringHashMap([]const u8) = undefined; - pub var rooms: [LEVELS]mapgen.Room.ArrayList = undefined; pub var stockpiles: [LEVELS]StockpileArrayList = undefined; pub var inputs: [LEVELS]StockpileArrayList = undefined; pub var outputs: [LEVELS]Rect.ArrayList = undefined; -pub const MapgenInfos = struct { - has_vault: bool = false, -}; -pub var mapgen_infos = [1]MapgenInfos{.{}} ** LEVELS; - -// Data objects pub var tasks: TaskArrayList = undefined; pub var squads: Squad.List = undefined; pub var mobs: MobList = undefined; @@ -171,6 +126,9 @@ pub var containers: ContainerList = undefined; pub var evocables: EvocableList = undefined; pub var messages: MessageArrayList = undefined; +pub var seed: u64 = undefined; +pub var floor_seeds: [LEVELS]u64 = undefined; + // Global variables pub var ticks: usize = 0; pub var player_turns: usize = 0; @@ -181,6 +139,56 @@ pub var destroyed_candles: usize = 0; pub var shrines_in_lockdown: [LEVELS]bool = [1]bool{false} ** LEVELS; pub var shrine_locations: [LEVELS]?Coord = [1]?Coord{null} ** LEVELS; +pub var dungeon: *Dungeon = undefined; +pub var layout: [LEVELS][HEIGHT][WIDTH]Layout = [1][HEIGHT][WIDTH]Layout{[1][WIDTH]Layout{[1]Layout{.Unknown} ** WIDTH} ** HEIGHT} ** LEVELS; +pub var state: GameState = .Game; +pub var current_level: usize = PLAYER_STARTING_LEVEL; +pub var player: *Mob = undefined; +pub var player_inited = false; + +pub var scoredata = std.enums.directEnumArray(scores.Stat, scores.StatValue, 0, undefined); +pub var threats: std.AutoHashMap(alert.Threat, alert.ThreatData) = undefined; +pub var responses: alert.ThreatResponse.AList = undefined; +pub var completed_events: [events.EVENTS.len]usize = [_]usize{0} ** events.EVENTS.len; + +// zig fmt: off +pub var night_rep = [types.Faction.TOTAL]isize{ + // + // NEC @ CG YSM NC + 0, 0, 0, -10, 10, + // +}; +// zig fmt: on + +pub var player_upgrades: [3]player_m.PlayerUpgradeInfo = undefined; +pub var player_conj_augments: [player_m.ConjAugment.TOTAL]player_m.ConjAugmentInfo = undefined; + +// Data files +pub const MapgenInfos = struct { + has_vault: bool = false, +}; +pub var mapgen_infos = [1]MapgenInfos{.{}} ** LEVELS; +pub var descriptions: std.StringHashMap([]const u8) = undefined; +// XXX: []u8 instead of '[]const u8` because of tsv parsing limits +pub const LevelInfo = struct { + id: []u8, + depth: usize, + shortname: []u8, + name: []u8, + upgr: bool, + optional: bool, + stairs: [Dungeon.MAX_STAIRS]?[]u8, +}; +pub var levelinfo: [LEVELS]LevelInfo = undefined; // data/levelinfo.tsv + +pub const StatusStringInfo = struct { + name: []const u8, + unliving_name: ?[]const u8, + mini_name: ?[]const u8, +}; +pub var status_str_infos: std.enums.EnumArray(Status, ?StatusStringInfo) = + std.enums.EnumArray(Status, ?StatusStringInfo).initFill(null); + // Find the nearest space near a coord in which a monster can be placed. // // Will *not* return crd.