208 lines
8 KiB
Zig
208 lines
8 KiB
Zig
pub const ecs = @import("ecs");
|
|
pub const rendering = @import("rendering");
|
|
pub const mods = @import("mods");
|
|
|
|
const Renderer = rendering.Renderer;
|
|
|
|
const api = @cImport({
|
|
@cInclude("sideros_api.h");
|
|
});
|
|
|
|
const std = @import("std");
|
|
|
|
const systems = @import("systems.zig");
|
|
|
|
var gpa: std.heap.DebugAllocator(.{}) = .{};
|
|
const allocator = gpa.allocator();
|
|
var pool: ecs.Pool = undefined;
|
|
var renderer: Renderer = undefined;
|
|
var camera: rendering.Camera = .{
|
|
.position = .{ 0.0, 5.0, -5.0 },
|
|
};
|
|
var input: ecs.Input = .{ .key_pressed = .{false} ** @intFromEnum(ecs.Input.KeyCode.menu) };
|
|
var resources: ecs.Resources = undefined;
|
|
const ModInfo = struct {
|
|
name: []const u8,
|
|
runtime: *mods.Runtime,
|
|
modIdx: u32,
|
|
};
|
|
var loadedMods: std.ArrayListUnmanaged(ModInfo) = .empty;
|
|
|
|
fn openOrCreateDir(fs: std.fs.Dir, path: []const u8) !std.fs.Dir {
|
|
var dir: std.fs.Dir = undefined;
|
|
dir = fs.openDir(path, .{ .iterate = true }) catch |err| {
|
|
if (err == std.fs.Dir.OpenError.FileNotFound) {
|
|
try fs.makeDir(path);
|
|
dir = try fs.openDir(path, .{ .iterate = true });
|
|
return dir;
|
|
} else {
|
|
return err;
|
|
}
|
|
};
|
|
return dir;
|
|
}
|
|
|
|
fn untarToDirAndGetFile(fs: std.fs.Dir, name: []const u8, unpack: []const u8) !std.fs.File {
|
|
var buffer: [1024]u8 = undefined;
|
|
var modDir = try openOrCreateDir(fs, unpack);
|
|
defer modDir.close();
|
|
var tarFile = try fs.openFile(try std.fmt.bufPrint(&buffer, "{s}.tar", .{name}), .{});
|
|
defer tarFile.close();
|
|
const tarData = try tarFile.readToEndAlloc(allocator, 1_000_000);
|
|
defer allocator.free(tarData);
|
|
var tarReader = std.io.Reader.fixed(tarData);
|
|
try std.tar.pipeToFileSystem(modDir, &tarReader, .{});
|
|
return try fs.openFile(try std.fmt.bufPrint(&buffer, "{s}/main.wasm", .{unpack}), .{});
|
|
}
|
|
|
|
fn loadMod(entry: std.fs.Dir.Entry) !void {
|
|
const modName = entry.name.ptr[0 .. entry.name.len - 4];
|
|
const fullDir = std.fmt.allocPrint(allocator, "assets/mods/{s}", .{modName}) catch @panic("Failed to allocate for fullDir");
|
|
defer allocator.free(fullDir);
|
|
const modDir = try std.fmt.allocPrint(allocator, "{s}_siderosmod__", .{fullDir});
|
|
const global_runtime = allocator.create(mods.GlobalRuntime) catch @panic("Failed to create global runtime");
|
|
global_runtime.* = mods.GlobalRuntime.init(allocator);
|
|
|
|
std.fs.cwd().deleteTree(modDir) catch |err| {
|
|
std.debug.panic("Failed to delete {s} (reason: {any})", .{ modDir, err });
|
|
};
|
|
var file = untarToDirAndGetFile(std.fs.cwd(), fullDir, modDir) catch |err| {
|
|
return err;
|
|
};
|
|
defer std.fs.cwd().deleteTree(modDir) catch |err| {
|
|
std.debug.panic("Failed to delete {s} (reason: {any})", .{ modDir, err });
|
|
};
|
|
defer file.close();
|
|
// TODO(luccie): Make this be able to construct a buffer for the whole file
|
|
const buffer = try allocator.alloc(u8, 1_000_000);
|
|
var reader = file.reader(buffer);
|
|
var parser = mods.Parser.init(allocator, &reader.interface) catch @panic("Failed to init parser");
|
|
defer parser.deinit();
|
|
parser.parseModule() catch |err| {
|
|
// TODO(luccie): Find a better option for the expression `parser.reader.buffer[parser.reader.seek]`
|
|
std.debug.print("[ERROR]: error {any} at byte {x}(0x{x})\n", .{ err, parser.reader.seek, parser.reader.buffer[parser.reader.seek] });
|
|
return err;
|
|
};
|
|
const module = parser.module();
|
|
|
|
for (0..parser.globalTypes.len) |i| {
|
|
global_runtime.addGlobal(@intCast(i), parser.globalTypes[i], parser.globalValues[i]) catch @panic("Failed to add runtime global");
|
|
}
|
|
|
|
var runtime = allocator.create(mods.Runtime) catch |err| {
|
|
std.debug.print("Failed to create runtime", .{});
|
|
return err;
|
|
};
|
|
runtime.* = mods.Runtime.init(allocator, module, global_runtime) catch |err| {
|
|
std.debug.print("Failed to init runtime", .{});
|
|
return err;
|
|
};
|
|
|
|
const modIdx: u32 = @intCast(loadedMods.items.len);
|
|
var parameters = [_]mods.VM.Value{.{ .i32 = @intCast(modIdx) }};
|
|
runtime.externalCall(allocator, .init, ¶meters) catch @panic("Failed to call to init");
|
|
const result = runtime.stack.pop().?.i64;
|
|
if (result != 0) {
|
|
std.debug.print("[ERROR]: Mod {s} init returned {d}\n", .{ modName, result });
|
|
return error.Failure;
|
|
}
|
|
loadedMods.append(allocator, .{ .name = try allocator.dupe(u8, modName), .runtime = runtime, .modIdx = modIdx }) catch @panic("Failed to append to loadedMods");
|
|
}
|
|
|
|
fn init_mods() void {
|
|
var modsDir = std.fs.cwd().openDir("./assets/mods", .{ .iterate = true }) catch @panic("Failed to open assets/mods");
|
|
defer modsDir.close();
|
|
|
|
var modsDirIter = modsDir.iterate();
|
|
while (modsDirIter.next() catch @panic("Failed to get next iteration of mods directory")) |entry| {
|
|
if (std.mem.indexOf(u8, entry.name, "siderosmod") != null) {
|
|
std.fs.cwd().deleteTree(entry.name) catch |err| {
|
|
std.debug.panic("Failed to delete {s} (reason: {any})", .{ entry.name, err });
|
|
};
|
|
continue;
|
|
}
|
|
if (entry.kind != std.fs.File.Kind.file) {
|
|
std.debug.panic("TODO: Search recursively for mods\n", .{});
|
|
}
|
|
const extension = entry.name.ptr[entry.name.len - 4 .. entry.name.len];
|
|
if (!std.mem.eql(u8, extension, ".tar")) {
|
|
std.debug.print("[WARNING]: Found non tar extension in mods directory\n", .{});
|
|
continue;
|
|
}
|
|
loadMod(entry) catch @panic("Failed to load mod");
|
|
}
|
|
}
|
|
|
|
fn sideros_init(io: std.Io, init: api.GameInit) void {
|
|
resources = .{
|
|
.camera = &camera,
|
|
.renderer = undefined,
|
|
.input = &input,
|
|
.terrain = undefined,
|
|
};
|
|
|
|
ecs.hooks.init(allocator) catch @panic("TODO: handle this");
|
|
ecs.hooks.addHook(.scroll, systems.zoomCamera) catch @panic("TODO handle this");
|
|
pool = ecs.Pool.init(allocator, &resources) catch @panic("TODO: Gracefully handle error");
|
|
// TODO(ernesto): I think this @ptrCast are unavoidable but maybe not?
|
|
renderer = Renderer.init(allocator, io, @ptrCast(init.instance), @ptrCast(init.surface)) catch |err| std.debug.panic("TODO: Gracefully handle error: {}\n", .{err});
|
|
|
|
resources.terrain = rendering.Terrain.init(allocator, renderer.device, .{
|
|
.octaves = 8,
|
|
.lacunarity = 2.0,
|
|
.gain = 0.6,
|
|
.scale = 30,
|
|
.multiplier = 1.0,
|
|
.exponent = 1.0,
|
|
|
|
.width = 200,
|
|
.height = 200,
|
|
.seed = 2497852058242342,
|
|
.resolution = 1,
|
|
}) catch @panic("TODO: handle this");
|
|
|
|
renderer.terrain_pipeline.setMaps(renderer.device, resources.terrain.texture) catch @panic("TODO: handle this");
|
|
|
|
pool.addSystemGroup(&[_]ecs.System{ systems.render, systems.moveCamera }, true) catch @panic("TODO: Gracefuly handle error");
|
|
pool.resources.renderer = &renderer;
|
|
pool.tick();
|
|
init_mods();
|
|
}
|
|
|
|
export fn sideros_update(gameUpdate: api.GameUpdate) callconv(.c) void {
|
|
_ = gameUpdate;
|
|
pool.tick();
|
|
}
|
|
|
|
export fn sideros_cleanup() callconv(.c) void {
|
|
for (loadedMods.items) |info| {
|
|
var runtime = info.runtime;
|
|
runtime.externalCall(allocator, .deinit, &.{}) catch @panic("Failed to call deinit");
|
|
const result = runtime.stack.pop().?.i64;
|
|
if (result != 0) {
|
|
std.debug.panic("[ERROR]: Mod {s} deinit returned {d}\n", .{ info.name, result });
|
|
}
|
|
defer runtime.deinit(allocator);
|
|
defer allocator.free(info.name);
|
|
}
|
|
loadedMods.deinit(allocator);
|
|
renderer.deinit();
|
|
pool.deinit();
|
|
if (gpa.deinit() != .ok) @panic("Memory leaked");
|
|
}
|
|
|
|
export fn sideros_key_callback(key: u32, release: bool) callconv(.c) void {
|
|
if (key <= @intFromEnum(ecs.Input.KeyCode.menu) and key >= @intFromEnum(ecs.Input.KeyCode.space)) {
|
|
if (release) {
|
|
input.key_pressed[key] = false;
|
|
} else {
|
|
input.key_pressed[key] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
export fn sideros_scroll_callback(up: bool) callconv(.c) void {
|
|
for (ecs.hooks.scroll.items) |hook| {
|
|
hook(&pool, if (up) .up else .down) catch @panic("TODO: actually handle this");
|
|
}
|
|
}
|