give up on this repo, rewrite on v2 without hot code reload
This commit is contained in:
parent
39c311e225
commit
5e9b4b43a9
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
zig-out
|
||||
zig-pkg
|
||||
.zig-cache
|
||||
|
||||
@ -48,9 +48,6 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
|
||||
build_options.addOption(bool, "code_dynamic_linking", code_dynamic_linking);
|
||||
build_options.addOption(bool, "code_static_linking", code_static_linking);
|
||||
build_options.addOption(bool, "asset_hot_reload", asset_hot_reload);
|
||||
if (asset_hot_reload) {
|
||||
build_options.addOptionPath("asset_dir", opts.asset_dir);
|
||||
}
|
||||
|
||||
const engine_lib = b.createModule(.{
|
||||
.root_source_file = b.path("src/game_lib/root.zig")
|
||||
@ -140,13 +137,17 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
|
||||
});
|
||||
const install_game_lib = b.addInstallArtifact(game_lib, .{});
|
||||
|
||||
run_cmd.addArg("--game-lib-path");
|
||||
run_cmd.addArg("--dynamic-lib-path");
|
||||
run_cmd.addArg(b.getInstallPath(.lib, game_lib.out_lib_filename));
|
||||
run_cmd.step.dependOn(&install_game_lib.step);
|
||||
|
||||
var game_lib_step = outer_builder.step("game-lib", "Build game dynamic library");
|
||||
game_lib_step.dependOn(&install_game_lib.step);
|
||||
}
|
||||
if (asset_hot_reload) {
|
||||
run_cmd.addArg("--assets-path");
|
||||
run_cmd.addFileArg(opts.asset_dir);
|
||||
}
|
||||
run_cmd_step = &run_cmd.step;
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,15 +106,16 @@ fn processEvent(self: *Environment, event: Input.Event) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stateInit(self: *Environment, initial: Init) void {
|
||||
pub fn stateInit(self: *Environment, initial: Init, assets: Lib.Asset.List) void {
|
||||
assert(self.frame == null);
|
||||
const opts = Lib.Init{
|
||||
.gpa = self.gpa,
|
||||
.seed = initial.seed,
|
||||
.assets = assets
|
||||
};
|
||||
self.state = self.callbacks.init(&opts);
|
||||
|
||||
self.frame = .init(self.gpa, initial.screen_size);
|
||||
self.frame = .init(self.gpa, initial.screen_size, assets);
|
||||
}
|
||||
|
||||
pub fn stateDeinit(self: *Environment) void {
|
||||
|
||||
@ -58,6 +58,18 @@ const Texture = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
rgba: [*]u8,
|
||||
|
||||
pub fn clone(self: Data, allocator: std.mem.Allocator) !Data {
|
||||
const pixel_count = self.width*self.height;
|
||||
const rgba = try allocator.dupe(u8, self.rgba[0..(pixel_count * 4)]);
|
||||
errdefer allocator.free(rgba);
|
||||
|
||||
return Data{
|
||||
.width = self.width,
|
||||
.height = self.height,
|
||||
.rgba = rgba.ptr,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn destroy(self: *Texture) void {
|
||||
@ -103,6 +115,9 @@ scissor_stack: std.ArrayList(Rect) = .empty,
|
||||
sokol_state: ?SokolState,
|
||||
sokol_options: SokolOptions,
|
||||
|
||||
deferred_textures_arena: std.heap.ArenaAllocator,
|
||||
deferred_textures: std.AutoArrayHashMapUnmanaged(Lib.Texture.Id, Texture.Data) = .empty,
|
||||
|
||||
const Options = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
logger: sg.Logger = .{},
|
||||
@ -113,6 +128,7 @@ pub fn init(options: Options) !Graphics {
|
||||
return Graphics{
|
||||
.gpa = options.allocator,
|
||||
.sokol_state = null,
|
||||
.deferred_textures_arena = std.heap.ArenaAllocator.init(options.allocator),
|
||||
.sokol_options = .{
|
||||
.logger = options.logger,
|
||||
.imgui_font = options.imgui_font,
|
||||
@ -198,6 +214,13 @@ pub fn setupSokol(self: *Graphics) !void {
|
||||
.nearest_sampler = nearest_sampler,
|
||||
.font_context = font_context,
|
||||
};
|
||||
|
||||
var iter = self.deferred_textures.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
try self.setTextureData(entry.key_ptr.*, entry.value_ptr.*);
|
||||
}
|
||||
_ = self.deferred_textures_arena.reset(.free_all);
|
||||
self.deferred_textures = .empty;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Graphics, gpa: std.mem.Allocator) void {
|
||||
@ -525,7 +548,9 @@ pub fn removeTexture(self: *Graphics, id: Lib.Texture.Id) void {
|
||||
|
||||
pub fn setTextureData(self: *Graphics, id: Lib.Texture.Id, data: Texture.Data) !void {
|
||||
if (self.sokol_state == null) {
|
||||
return error.SokolNotSetup;
|
||||
const arena = self.deferred_textures_arena.allocator();
|
||||
try self.deferred_textures.put(arena, id, try data.clone(arena));
|
||||
return;
|
||||
}
|
||||
|
||||
const texture = self.textures.get(.fromInt(@intFromEnum(id))) orelse return;
|
||||
|
||||
@ -36,14 +36,126 @@ const Environment = @import("environment.zig");
|
||||
|
||||
const Engine = @This();
|
||||
|
||||
const IniReader = struct {
|
||||
reader: *std.Io.Reader,
|
||||
|
||||
const Tag = union(enum) {
|
||||
section: []const u8,
|
||||
option: struct {
|
||||
key: []const u8,
|
||||
value: []const u8
|
||||
},
|
||||
|
||||
pub fn isOption(self: Tag, key: []const u8) ?[]const u8 {
|
||||
if (self == .option and std.mem.eql(u8, self.option.key, key)) {
|
||||
return self.option.value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn next(self: *IniReader) !?Tag {
|
||||
while (true) {
|
||||
var line = self.reader.takeDelimiterInclusive('\n') catch |e| switch (e) {
|
||||
error.EndOfStream => break,
|
||||
else => return e
|
||||
};
|
||||
line = @constCast(std.mem.trim(u8, line, &std.ascii.whitespace));
|
||||
if (std.mem.startsWith(u8, line, "#")) {
|
||||
continue;
|
||||
}
|
||||
if (line.len == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std.mem.startsWith(u8, line, "[") and std.mem.endsWith(u8, line, "]")) {
|
||||
const section_name = line[1..(line.len-1)];
|
||||
return Tag{
|
||||
.section = std.mem.trim(u8, section_name, &std.ascii.whitespace)
|
||||
};
|
||||
}
|
||||
if (std.mem.indexOfScalar(u8, line, '=')) |equals_index| {
|
||||
const key = line[0..equals_index];
|
||||
const value = line[(equals_index+1)..];
|
||||
|
||||
return Tag{
|
||||
.option = .{
|
||||
.key = std.mem.trim(u8, key, &std.ascii.whitespace),
|
||||
.value = std.mem.trim(u8, value, &std.ascii.whitespace),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return error.InvalidLine;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const LoadOptions = struct {
|
||||
asset_dir: std.fs.Dir,
|
||||
ini_reader: *IniReader,
|
||||
arena: *std.heap.ArenaAllocator
|
||||
};
|
||||
|
||||
pub const AssetSystem = struct {
|
||||
assets: std.ArrayList(Lib.Asset),
|
||||
|
||||
pub fn register(self: AssetSystem) void {
|
||||
_ = self; // autofix
|
||||
}
|
||||
};
|
||||
|
||||
pub const Subsystems = struct {
|
||||
graphics: *Graphics,
|
||||
audio: *Audio,
|
||||
assets: *AssetSystem,
|
||||
};
|
||||
|
||||
const ImageAsset = struct {
|
||||
pub fn load(subsystems: Subsystems, opts: LoadOptions) !void {
|
||||
const ini_reader = opts.ini_reader;
|
||||
const arena = opts.arena;
|
||||
const dir = opts.asset_dir;
|
||||
|
||||
var maybe_image_path: ?[]const u8 = null;
|
||||
while (try ini_reader.next()) |tag| {
|
||||
if (tag.isOption("path")) |opt| {
|
||||
maybe_image_path = try arena.allocator().dupe(u8, opt);
|
||||
}
|
||||
}
|
||||
|
||||
if (maybe_image_path) |image_path| {
|
||||
const image_data = try readFileAll(arena.allocator(), dir, image_path);
|
||||
const image = try STBImage.load(image_data);
|
||||
defer image.deinit();
|
||||
std.debug.print("{s}\n", .{image_path});
|
||||
const texture_id = try subsystems.graphics.addTexture();
|
||||
try subsystems.graphics.setTextureData(texture_id, .{
|
||||
.width = image.width,
|
||||
.height = image.height,
|
||||
.rgba = image.rgba8_pixels,
|
||||
});
|
||||
}
|
||||
// subsystems.assets.register("icon");
|
||||
// try subsystems.assets.append(self.assets_arena.allocator(), Lib.Asset{
|
||||
// .id = @enumFromInt(self.assets.items.len),
|
||||
// .key = try self.assets_arena.allocator().dupe(u8, asset_id),
|
||||
// .data = &[_]u8{}
|
||||
// });
|
||||
}
|
||||
};
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
graphics: Graphics,
|
||||
audio: Audio,
|
||||
|
||||
seed: u64,
|
||||
// game_linking: GameLinking,
|
||||
environment: Environment,
|
||||
|
||||
assets_arena: std.heap.ArenaAllocator,
|
||||
assets: std.ArrayList(Lib.Asset),
|
||||
|
||||
queued_events: std.ArrayList(Input.Event),
|
||||
is_mouse_inside: bool,
|
||||
|
||||
@ -51,28 +163,23 @@ debug: DebugSystem,
|
||||
|
||||
pub const RunOptions = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
game_library_path: ?[]const u8 = null,
|
||||
do_recording: bool = (builtin.mode == .Debug),
|
||||
screen_width: u32 = 640,
|
||||
screen_height: u32 = 480,
|
||||
|
||||
dynamic_library_path: ?[]const u8 = null,
|
||||
assets_path: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub fn run(self: *Engine, opts: RunOptions) !void {
|
||||
// var game_linking: GameLinking = undefined;
|
||||
// if (opts.game_library_path) |game_library_path| {
|
||||
// game_linking = try .initDynamic(opts.allocator, game_library_path);
|
||||
// } else {
|
||||
// game_linking = try .initStatic();
|
||||
// }
|
||||
|
||||
var debug_system = DebugSystem.init(opts.allocator);
|
||||
|
||||
var maybe_callbacks: ?GameCallbacks = null;
|
||||
if (build_options.code_static_linking) {
|
||||
maybe_callbacks = GameLinking.getStatic();
|
||||
}
|
||||
if (build_options.code_dynamic_linking and opts.game_library_path != null) {
|
||||
var dynamic = try GameLinking.Dynamic.init(opts.allocator, opts.game_library_path.?);
|
||||
if (build_options.code_dynamic_linking and opts.dynamic_library_path != null) {
|
||||
var dynamic = try GameLinking.Dynamic.init(opts.allocator, opts.dynamic_library_path.?);
|
||||
maybe_callbacks = try dynamic.getCallbacks();
|
||||
debug_system.dynamic_linking = dynamic;
|
||||
}
|
||||
@ -105,10 +212,62 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
|
||||
.queued_events = .empty,
|
||||
.seed = initial.seed,
|
||||
.is_mouse_inside = false,
|
||||
.debug = debug_system
|
||||
.debug = debug_system,
|
||||
.assets = .empty,
|
||||
.assets_arena = std.heap.ArenaAllocator.init(opts.allocator)
|
||||
};
|
||||
|
||||
self.environment.stateInit(initial);
|
||||
if (opts.assets_path) |assets_path| {
|
||||
var temp_arena = std.heap.ArenaAllocator.init(opts.allocator);
|
||||
defer temp_arena.deinit();
|
||||
|
||||
var result_arena = std.heap.ArenaAllocator.init(opts.allocator);
|
||||
defer result_arena.deinit();
|
||||
|
||||
var dir = try std.fs.openDirAbsolute(assets_path, .{ .iterate = false });
|
||||
defer dir.close();
|
||||
|
||||
const result = try listFilesRecursively(assets_path, temp_arena.allocator(), result_arena.allocator(), ".ini");
|
||||
for (result.items) |path| {
|
||||
const path_without_ini = path[0..(path.len-4)];
|
||||
const index_of_dot = std.mem.lastIndexOfScalar(u8, path_without_ini, '.') orelse continue;
|
||||
const asset_type = path_without_ini[(index_of_dot+1)..];
|
||||
const asset_id = path_without_ini[0..index_of_dot];
|
||||
|
||||
std.debug.print("{s} {s}\n", .{asset_id, asset_type});
|
||||
if (std.mem.eql(u8, asset_type, "image")) {
|
||||
const f = try dir.openFile(path, .{ });
|
||||
defer f.close();
|
||||
|
||||
var reader_buffer: [Math.bytes_per_kib * 4]u8 = undefined;
|
||||
var file_reader = f.reader(&reader_buffer);
|
||||
var ini_reader = IniReader{ .reader = &file_reader.interface };
|
||||
|
||||
var asset_system = AssetSystem{
|
||||
.assets = .empty
|
||||
};
|
||||
try ImageAsset.load(.{
|
||||
.audio = &self.audio,
|
||||
.graphics = &self.graphics,
|
||||
.assets = &asset_system
|
||||
},
|
||||
.{
|
||||
.arena = &temp_arena,
|
||||
.ini_reader = &ini_reader,
|
||||
.asset_dir = dir
|
||||
});
|
||||
}
|
||||
}
|
||||
// for (result)
|
||||
}
|
||||
|
||||
try self.assets.append(self.allocator, Lib.Asset{
|
||||
.data = &.{}
|
||||
});
|
||||
|
||||
self.environment.stateInit(initial, .{
|
||||
.items = self.assets.items
|
||||
});
|
||||
|
||||
// TODO:
|
||||
// if (opts.do_recording) {
|
||||
@ -169,6 +328,30 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
|
||||
});
|
||||
}
|
||||
|
||||
fn listFilesRecursively(
|
||||
absolute_path: []const u8,
|
||||
temp_allocator: std.mem.Allocator,
|
||||
result_arena: std.mem.Allocator,
|
||||
extension: []const u8
|
||||
) !std.ArrayList([]u8) {
|
||||
var result: std.ArrayList([]u8) = .empty;
|
||||
var dir = try std.fs.openDirAbsolute(absolute_path, .{ .iterate = true });
|
||||
defer dir.close();
|
||||
|
||||
var walker = try dir.walk(temp_allocator);
|
||||
defer walker.deinit();
|
||||
|
||||
while (try walker.next()) |entry| {
|
||||
if (entry.kind == .file) {
|
||||
if (std.mem.endsWith(u8, entry.path, extension)) {
|
||||
try result.append(result_arena, try result_arena.dupe(u8, entry.path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn readFileAll(gpa: std.mem.Allocator, dir: std.fs.Dir, sub_path: []const u8) ![]u8 {
|
||||
const f = try dir.openFile(sub_path, .{});
|
||||
defer f.close();
|
||||
@ -206,6 +389,7 @@ fn sokolCleanup(self: *Engine) void {
|
||||
self.audio.deinit();
|
||||
self.graphics.deinit(self.allocator);
|
||||
self.debug.deinit();
|
||||
self.assets_arena.deinit();
|
||||
// TODO:
|
||||
// self.game_linking.deinit(self.allocator);
|
||||
}
|
||||
|
||||
@ -270,6 +270,27 @@ pub const KeyState = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Asset = struct {
|
||||
data: []const u8,
|
||||
// type: *std.builtin.Type,
|
||||
|
||||
pub const Id = u16;
|
||||
|
||||
pub const List = struct {
|
||||
items: []Asset,
|
||||
|
||||
pub fn get(self: List, comptime T: type, id: Id) *const T {
|
||||
if (id >= self.items.len) {
|
||||
@panic("foo");
|
||||
}
|
||||
if (@sizeOf(T) != self.items[id].data.len) {
|
||||
@panic("foo");
|
||||
}
|
||||
return @ptrCast(@alignCast(self.items[id].data.ptr));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub const Frame = struct {
|
||||
arena: std.heap.ArenaAllocator,
|
||||
time_ns: Nanoseconds,
|
||||
@ -288,9 +309,12 @@ pub const Frame = struct {
|
||||
screen_size: Vec2,
|
||||
clear_color: Vec4,
|
||||
|
||||
assets: Asset.List,
|
||||
|
||||
pub fn init(
|
||||
gpa: std.mem.Allocator,
|
||||
screen_size: Vec2
|
||||
screen_size: Vec2,
|
||||
assets: Asset.List,
|
||||
) Frame {
|
||||
return Frame{
|
||||
.arena = std.heap.ArenaAllocator.init(gpa),
|
||||
@ -306,6 +330,7 @@ pub const Frame = struct {
|
||||
.audio_commands = .empty,
|
||||
.hide_cursor = false,
|
||||
.screen_size = screen_size,
|
||||
.assets = assets
|
||||
};
|
||||
}
|
||||
|
||||
@ -473,6 +498,7 @@ pub const Frame = struct {
|
||||
pub const Init = struct {
|
||||
gpa: std.mem.Allocator,
|
||||
seed: u64,
|
||||
assets: Asset.List,
|
||||
};
|
||||
|
||||
pub const Callbacks = struct {
|
||||
|
||||
@ -29,6 +29,9 @@ pub fn main() !void {
|
||||
var game_library_path: ?[]const u8 = null;
|
||||
defer if (game_library_path) |str| allocator.free(str);
|
||||
|
||||
var assets_path: ?[]const u8 = null;
|
||||
defer if (assets_path) |str| allocator.free(str);
|
||||
|
||||
// var asset_loading: Engine.RunOptions.AssetLoading = undefined;
|
||||
// if (!build_options.asset_hot_reload) {
|
||||
// asset_loading = .{
|
||||
@ -53,12 +56,21 @@ pub fn main() !void {
|
||||
var opt_game_lib_path: ?CLI.Option.Id = null;
|
||||
if (build_options.code_dynamic_linking) {
|
||||
opt_game_lib_path = try cli.addOption(.{
|
||||
.long_name = "game-lib-path",
|
||||
.long_name = "dynamic-lib-path",
|
||||
.description = "Load game code from dynamic library",
|
||||
.kind = .single_argument
|
||||
});
|
||||
}
|
||||
|
||||
var opt_assets_path: ?CLI.Option.Id = null;
|
||||
if (build_options.asset_hot_reload) {
|
||||
opt_assets_path = try cli.addOption(.{
|
||||
.long_name = "assets-path",
|
||||
.description = "Load assets from folder at runtime",
|
||||
.kind = .single_argument
|
||||
});
|
||||
}
|
||||
|
||||
const cmd_version = try cli.addCommand(.{
|
||||
.name = "version",
|
||||
.description = "Show semantic version and build options"
|
||||
@ -85,6 +97,12 @@ pub fn main() !void {
|
||||
}
|
||||
}
|
||||
|
||||
if (opt_assets_path) |opt| {
|
||||
if (parsed.getOptionArgument(opt)) |arg| {
|
||||
assets_path = try allocator.dupe(u8, arg);
|
||||
}
|
||||
}
|
||||
|
||||
const command = parsed.command orelse cmd_launch;
|
||||
if (command == cmd_version) {
|
||||
cli.stdout.write("Version: {s}\n", .{ "TODO:" });
|
||||
@ -102,8 +120,8 @@ pub fn main() !void {
|
||||
|
||||
try engine.run(.{
|
||||
.allocator = allocator,
|
||||
.game_library_path = game_library_path,
|
||||
// .asset_loading = asset_loading
|
||||
.dynamic_library_path = game_library_path,
|
||||
.assets_path = assets_path
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
4
src/assets/icon.image.ini
Normal file
4
src/assets/icon.image.ini
Normal file
@ -0,0 +1,4 @@
|
||||
[asdf]
|
||||
path = ./icon.png
|
||||
|
||||
# afd
|
||||
24
src/game.zig
24
src/game.zig
@ -14,6 +14,22 @@ const State = struct {
|
||||
player: Vec2,
|
||||
};
|
||||
|
||||
const AssetId = enum {
|
||||
icon
|
||||
};
|
||||
|
||||
const Image = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
rgba: []const u8,
|
||||
|
||||
pub const nil = Image{
|
||||
.width = 0,
|
||||
.height = 0,
|
||||
.rgba = &.{ }
|
||||
};
|
||||
};
|
||||
|
||||
pub fn init(opts: *const Engine.Init) callconv(.c) *anyopaque {
|
||||
const gpa = opts.gpa;
|
||||
|
||||
@ -23,6 +39,14 @@ pub fn init(opts: *const Engine.Init) callconv(.c) *anyopaque {
|
||||
.player = .init(50, 50),
|
||||
};
|
||||
|
||||
// var icon = opts.assets.get(ImageAsset, "icon");
|
||||
// icon.id
|
||||
// icon.rgba
|
||||
// icon.texture_id
|
||||
|
||||
// std.debug.print("{}\n", .{opts.assets});
|
||||
// const icon = opts.assets.get(Image, @intFromEnum(AssetId.icon));
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user