Got ecs and assets imported
parent
1d430e81f7
commit
0f56ac1f2c
|
@ -0,0 +1,8 @@
|
||||||
|
{{#sprites}}
|
||||||
|
// {{name}}
|
||||||
|
pub const {{name}}_width = {{width}};
|
||||||
|
pub const {{name}}_height = {{height}};
|
||||||
|
pub const {{name}}_flags = {{flags}}; // {{flagsHumanReadable}}
|
||||||
|
pub const {{name}} = [{{length}}]u8{ {{bytes}} };
|
||||||
|
|
||||||
|
{{/sprites}}
|
|
@ -0,0 +1,2 @@
|
||||||
|
// pub const tiles = @import("tiles.zig");
|
||||||
|
pub usingnamespace @import("sprites.zig");
|
Binary file not shown.
After Width: | Height: | Size: 277 B |
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 1011 B |
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
w4 png2src --template assets/assets-template --zig assets/tiles.png -o assets/tiles.zig
|
||||||
|
w4 png2src --template assets/assets-template --zig assets/sprites.png -o assets/sprites.zig
|
|
@ -45,9 +45,15 @@ test "stack version check" {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(b: *std.build.Builder) !void {
|
pub fn build(b: *std.build.Builder) !void {
|
||||||
|
const assets = std.build.Pkg{
|
||||||
|
.name = "assets",
|
||||||
|
.path = .{ .path = "assets/assets.zig" },
|
||||||
|
};
|
||||||
|
|
||||||
const zig_version = @import("builtin").zig_version;
|
const zig_version = @import("builtin").zig_version;
|
||||||
const mode = b.standardReleaseOptions();
|
const mode = b.standardReleaseOptions();
|
||||||
const lib = b.addSharedLibrary("cart", "src/main.zig", .unversioned);
|
const lib = b.addSharedLibrary("cart", "src/main.zig", .unversioned);
|
||||||
|
lib.addPackage(assets);
|
||||||
lib.setBuildMode(mode);
|
lib.setBuildMode(mode);
|
||||||
lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
|
lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
|
||||||
lib.import_memory = true;
|
lib.import_memory = true;
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const ArgsTuple = std.meta.Tuple;
|
||||||
|
const Tuple = std.meta.Tuple;
|
||||||
|
|
||||||
|
pub fn World(comptime ComponentBase: type) type {
|
||||||
|
// Build a component type at comptime based off of ComponentBase. It makes all the fields
|
||||||
|
// nullable so they are easy to pull out of the store.
|
||||||
|
const Component = componentConstructor: {
|
||||||
|
var fields = std.meta.fields(ComponentBase);
|
||||||
|
var newFields: [fields.len]std.builtin.TypeInfo.StructField = undefined;
|
||||||
|
inline for (fields) |field, i| {
|
||||||
|
const T = field.field_type;
|
||||||
|
const default: ?T = null;
|
||||||
|
newFields[i] = std.builtin.TypeInfo.StructField{
|
||||||
|
.name = field.name,
|
||||||
|
.field_type = ?T,
|
||||||
|
.default_value = default,
|
||||||
|
.is_comptime = false,
|
||||||
|
.alignment = if (@sizeOf(T) > 0) @alignOf(T) else 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break :componentConstructor @Type(.{ .Struct = .{
|
||||||
|
.layout = .Auto,
|
||||||
|
.fields = &newFields,
|
||||||
|
.decls = &[_]std.builtin.TypeInfo.Declaration{},
|
||||||
|
.is_tuple = false,
|
||||||
|
} });
|
||||||
|
};
|
||||||
|
return struct {
|
||||||
|
components: ComponentPool,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
pub const Query = ComponentQuery;
|
||||||
|
|
||||||
|
const ComponentPool = std.MultiArrayList(Component);
|
||||||
|
const ComponentEnum = std.meta.FieldEnum(Component);
|
||||||
|
const ComponentSet = std.EnumSet(ComponentEnum);
|
||||||
|
const ComponentQuery = struct {
|
||||||
|
required: ComponentSet = ComponentSet.init(.{}),
|
||||||
|
excluded: ComponentSet = ComponentSet.init(.{}),
|
||||||
|
|
||||||
|
pub fn init() @This() {
|
||||||
|
return @This(){};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn query(require_set: []const ComponentEnum, exclude_set: []const ComponentEnum) @This() {
|
||||||
|
var this = @This(){};
|
||||||
|
for (require_set) |f| {
|
||||||
|
this.required.insert(f);
|
||||||
|
}
|
||||||
|
for (exclude_set) |f| {
|
||||||
|
this.excluded.insert(f);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require(set: []const ComponentEnum) @This() {
|
||||||
|
var this = @This(){};
|
||||||
|
for (set) |f| {
|
||||||
|
this.required.insert(f);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exclude(set: []const ComponentEnum) @This() {
|
||||||
|
var this = @This(){};
|
||||||
|
for (set) |f| {
|
||||||
|
this.excluded.insert(f);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = std.meta.fields(Component);
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) @This() {
|
||||||
|
return @This(){
|
||||||
|
.components = ComponentPool{},
|
||||||
|
.alloc = alloc,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(this: *@This(), component: Component) usize {
|
||||||
|
const len = this.components.len;
|
||||||
|
this.components.append(this.alloc, component) catch unreachable;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(this: *@This(), entity: usize) void {
|
||||||
|
// TODO
|
||||||
|
_ = this;
|
||||||
|
_ = entity;
|
||||||
|
@compileError("unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(this: *@This(), entity: usize, component: ComponentEnum) *Component {
|
||||||
|
return this.components.items(component)[entity];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enum2type(comptime enumList: []const ComponentEnum) []type {
|
||||||
|
var t: [enumList.len]type = undefined;
|
||||||
|
inline for (enumList) |e, i| {
|
||||||
|
const field_type = @typeInfo(fields[@enumToInt(e)].field_type);
|
||||||
|
t[i] = *field_type.Optional.child;
|
||||||
|
}
|
||||||
|
return &t;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process(this: *@This(), dt: f32, comptime comp: []const ComponentEnum, func: anytype) void {
|
||||||
|
const Args = Tuple([_]type{f32} ++ enum2type(comp));
|
||||||
|
var i = this.iter(Query.require(comp));
|
||||||
|
while (i.next()) |e| {
|
||||||
|
var args: Args = undefined;
|
||||||
|
args[0] = dt;
|
||||||
|
inline for (comp) |f, j| {
|
||||||
|
args[j + 1] = &(@field(e, @tagName(f)).?);
|
||||||
|
}
|
||||||
|
@call(.{}, func, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iterAll(this: *@This()) Iterator {
|
||||||
|
return Iterator.init(this, ComponentQuery{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(this: *@This(), query: ComponentQuery) Iterator {
|
||||||
|
return Iterator.init(this, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
const Iterator = struct {
|
||||||
|
world: *Self,
|
||||||
|
lastComponent: ?Component,
|
||||||
|
index: usize,
|
||||||
|
query: ComponentQuery,
|
||||||
|
|
||||||
|
pub fn init(w: *Self, q: ComponentQuery) @This() {
|
||||||
|
return @This(){
|
||||||
|
.world = w,
|
||||||
|
.lastComponent = null,
|
||||||
|
.index = 0,
|
||||||
|
.query = q,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(this: *@This()) ?*Component {
|
||||||
|
if (this.lastComponent) |e| this.world.components.set(this.index - 1, e);
|
||||||
|
if (this.index == this.world.components.len) return null;
|
||||||
|
var match = false;
|
||||||
|
while (!match) {
|
||||||
|
if (this.index == this.world.components.len) return null;
|
||||||
|
this.lastComponent = this.world.components.get(this.index);
|
||||||
|
match = true;
|
||||||
|
inline for (fields) |f| {
|
||||||
|
const fenum = std.meta.stringToEnum(ComponentEnum, f.name) orelse unreachable;
|
||||||
|
const required = this.query.required.contains(fenum);
|
||||||
|
const excluded = this.query.excluded.contains(fenum);
|
||||||
|
const has = @field(this.lastComponent.?, f.name) != null;
|
||||||
|
if ((required and !has) or (excluded and has)) {
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.index += 1;
|
||||||
|
}
|
||||||
|
return &this.lastComponent.?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
55
src/main.zig
55
src/main.zig
|
@ -1,25 +1,52 @@
|
||||||
|
const std = @import("std");
|
||||||
const w4 = @import("wasm4.zig");
|
const w4 = @import("wasm4.zig");
|
||||||
|
const ecs = @import("ecs.zig");
|
||||||
|
const assets = @import("assets");
|
||||||
|
|
||||||
const smiley = [8]u8{
|
const Vec2f = std.meta.Vector(2, f32);
|
||||||
0b11000011,
|
const Pos = Vec2f;
|
||||||
0b10000001,
|
const Control = enum { player };
|
||||||
0b00100100,
|
const Component = struct {
|
||||||
0b00100100,
|
pos: Pos,
|
||||||
0b00000000,
|
control: Control,
|
||||||
0b00100100,
|
|
||||||
0b10011001,
|
|
||||||
0b11000011,
|
|
||||||
};
|
};
|
||||||
|
const World = ecs.World(Component);
|
||||||
|
|
||||||
|
const KB = 1024;
|
||||||
|
var heap: [1 * KB]u8 = undefined;
|
||||||
|
var fba = std.heap.FixedBufferAllocator.init(&heap);
|
||||||
|
var world: World = World.init(fba.allocator());
|
||||||
|
|
||||||
|
export fn start() void {
|
||||||
|
_ = world.create(.{ .pos = .{ 76, 76 }, .control = .player });
|
||||||
|
}
|
||||||
|
|
||||||
export fn update() void {
|
export fn update() void {
|
||||||
w4.DRAW_COLORS.* = 2;
|
w4.DRAW_COLORS.* = 2;
|
||||||
w4.text("Hello from Zig!", 10, 10);
|
w4.text("Hello from Zig!", .{ 10, 10 });
|
||||||
|
|
||||||
const gamepad = w4.GAMEPAD1.*;
|
if (w4.GAMEPAD1.button_1) {
|
||||||
if (gamepad & w4.BUTTON_1 != 0) {
|
|
||||||
w4.DRAW_COLORS.* = 4;
|
w4.DRAW_COLORS.* = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
w4.blit(&smiley, 76, 76, 8, 8, w4.BLIT_1BPP);
|
world.process(1, &.{ .pos, .control }, controlProcess);
|
||||||
w4.text("Press X to blink", 16, 90);
|
world.process(1, &.{.pos}, drawProcess);
|
||||||
|
|
||||||
|
w4.DRAW_COLORS.* = 2;
|
||||||
|
// w4.blit(&smiley, .{ 76, 76 }, .{ 8, 8 }, .{ .bpp = .b1 });
|
||||||
|
w4.text("Press X to blink", .{ 16, 90 });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drawProcess(_: f32, pos: *Pos) void {
|
||||||
|
w4.DRAW_COLORS.* = 0x0030;
|
||||||
|
w4.externs.blitSub(&assets.sprites, @floatToInt(i32, pos.*[0]), @floatToInt(i32, pos.*[1]), 8, 8, 0, 0, 128, assets.sprites_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn controlProcess(_: f32, pos: *Pos, control: *Control) void {
|
||||||
|
_ = control;
|
||||||
|
if (w4.GAMEPAD1.button_up) pos.*[1] -= 1;
|
||||||
|
if (w4.GAMEPAD1.button_down) pos.*[1] += 1;
|
||||||
|
if (w4.GAMEPAD1.button_left) pos.*[0] -= 1;
|
||||||
|
if (w4.GAMEPAD1.button_right) pos.*[0] += 1;
|
||||||
|
// w4.trace("here", .{});
|
||||||
}
|
}
|
||||||
|
|
340
src/wasm4.zig
340
src/wasm4.zig
|
@ -1,13 +1,102 @@
|
||||||
//
|
//! Stolen from pfgithub's wasm4-zig repo
|
||||||
// WASM-4: https://wasm4.org/docs
|
//! https://github.com/pfgithub/wasm4-zig
|
||||||
|
|
||||||
// ┌───────────────────────────────────────────────────────────────────────────┐
|
const w4 = @This();
|
||||||
// │ │
|
const std = @import("std");
|
||||||
// │ Platform Constants │
|
|
||||||
// │ │
|
|
||||||
// └───────────────────────────────────────────────────────────────────────────┘
|
|
||||||
|
|
||||||
pub const CANVAS_SIZE: u32 = 160;
|
/// PLATFORM CONSTANTS
|
||||||
|
pub const CANVAS_SIZE = 160;
|
||||||
|
|
||||||
|
/// Helpers
|
||||||
|
pub const Vec2 = @import("std").meta.Vector(2, i32);
|
||||||
|
pub const x = 0;
|
||||||
|
pub const y = 1;
|
||||||
|
|
||||||
|
pub fn texLen(size: Vec2) usize {
|
||||||
|
return @intCast(usize, std.math.divCeil(i32, size[x] * size[y] * 2, 8) catch unreachable);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Mbl = enum { mut, cons };
|
||||||
|
pub fn Tex(comptime mbl: Mbl) type {
|
||||||
|
return struct {
|
||||||
|
// oh that's really annoying…
|
||||||
|
// ideally there would be a way to have a readonly Tex and a mutable Tex
|
||||||
|
// and the mutable should implicit cast to readonly
|
||||||
|
data: switch (mbl) {
|
||||||
|
.mut => [*]u8,
|
||||||
|
.cons => [*]const u8,
|
||||||
|
},
|
||||||
|
size: Vec2,
|
||||||
|
|
||||||
|
pub fn wrapSlice(slice: switch (mbl) {
|
||||||
|
.mut => []u8,
|
||||||
|
.cons => []const u8,
|
||||||
|
}, size: Vec2) Tex(mbl) {
|
||||||
|
if (slice.len != texLen(size)) {
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
return .{
|
||||||
|
.data = slice.ptr,
|
||||||
|
.size = size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cons(tex: Tex(.mut)) Tex(.cons) {
|
||||||
|
return .{
|
||||||
|
.data = tex.data,
|
||||||
|
.size = tex.size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blit(dest: Tex(.mut), dest_ul: Vec2, src: Tex(.cons), src_ul: Vec2, src_wh: Vec2, remap_colors: [4]u3, scale: Vec2) void {
|
||||||
|
for (range(@intCast(usize, src_wh[y]))) |_, y_usz| {
|
||||||
|
const yp = @intCast(i32, y_usz);
|
||||||
|
for (range(@intCast(usize, src_wh[x]))) |_, x_usz| {
|
||||||
|
const xp = @intCast(i32, x_usz);
|
||||||
|
const pos = Vec2{ xp, yp };
|
||||||
|
|
||||||
|
const value = remap_colors[src.get(src_ul + pos)];
|
||||||
|
if (value <= std.math.maxInt(u2)) {
|
||||||
|
dest.rect(pos * scale + dest_ul, scale, @intCast(u2, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn rect(dest: Tex(.mut), ul: Vec2, wh: Vec2, color: u2) void {
|
||||||
|
for (range(std.math.lossyCast(usize, wh[y]))) |_, y_usz| {
|
||||||
|
const yp = @intCast(i32, y_usz);
|
||||||
|
for (range(std.math.lossyCast(usize, wh[x]))) |_, x_usz| {
|
||||||
|
const xp = @intCast(i32, x_usz);
|
||||||
|
|
||||||
|
dest.set(ul + Vec2{ xp, yp }, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get(tex: Tex(mbl), pos: Vec2) u2 {
|
||||||
|
if (@reduce(.Or, pos < w4.Vec2{ 0, 0 })) return 0;
|
||||||
|
if (@reduce(.Or, pos >= tex.size)) return 0;
|
||||||
|
const index_unscaled = pos[w4.x] + (pos[w4.y] * tex.size[w4.x]);
|
||||||
|
const index = @intCast(usize, @divFloor(index_unscaled, 4));
|
||||||
|
const byte_idx = @intCast(u3, (@mod(index_unscaled, 4)) * 2);
|
||||||
|
return @truncate(u2, tex.data[index] >> byte_idx);
|
||||||
|
}
|
||||||
|
pub fn set(tex: Tex(.mut), pos: Vec2, value: u2) void {
|
||||||
|
if (@reduce(.Or, pos < w4.Vec2{ 0, 0 })) return;
|
||||||
|
if (@reduce(.Or, pos >= tex.size)) return;
|
||||||
|
const index_unscaled = pos[w4.x] + (pos[w4.y] * tex.size[w4.x]);
|
||||||
|
const index = @intCast(usize, @divFloor(index_unscaled, 4));
|
||||||
|
const byte_idx = @intCast(u3, (@mod(index_unscaled, 4)) * 2);
|
||||||
|
tex.data[index] &= ~(@as(u8, 0b11) << byte_idx);
|
||||||
|
tex.data[index] |= @as(u8, value) << byte_idx;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn range(len: usize) []const void {
|
||||||
|
return @as([*]const void, &[_]void{})[0..len];
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub const Tex1BPP = struct {…};
|
||||||
|
|
||||||
// ┌───────────────────────────────────────────────────────────────────────────┐
|
// ┌───────────────────────────────────────────────────────────────────────────┐
|
||||||
// │ │
|
// │ │
|
||||||
|
@ -17,26 +106,71 @@ pub const CANVAS_SIZE: u32 = 160;
|
||||||
|
|
||||||
pub const PALETTE: *[4]u32 = @intToPtr(*[4]u32, 0x04);
|
pub const PALETTE: *[4]u32 = @intToPtr(*[4]u32, 0x04);
|
||||||
pub const DRAW_COLORS: *u16 = @intToPtr(*u16, 0x14);
|
pub const DRAW_COLORS: *u16 = @intToPtr(*u16, 0x14);
|
||||||
pub const GAMEPAD1: *const u8 = @intToPtr(*const u8, 0x16);
|
pub const GAMEPAD1: *const Gamepad = @intToPtr(*const Gamepad, 0x16);
|
||||||
pub const GAMEPAD2: *const u8 = @intToPtr(*const u8, 0x17);
|
pub const GAMEPAD2: *const Gamepad = @intToPtr(*const Gamepad, 0x17);
|
||||||
pub const GAMEPAD3: *const u8 = @intToPtr(*const u8, 0x18);
|
pub const GAMEPAD3: *const Gamepad = @intToPtr(*const Gamepad, 0x18);
|
||||||
pub const GAMEPAD4: *const u8 = @intToPtr(*const u8, 0x19);
|
pub const GAMEPAD4: *const Gamepad = @intToPtr(*const Gamepad, 0x19);
|
||||||
pub const MOUSE_X: *const i16 = @intToPtr(*const i16, 0x1a);
|
|
||||||
pub const MOUSE_Y: *const i16 = @intToPtr(*const i16, 0x1c);
|
|
||||||
pub const MOUSE_BUTTONS: *const u8 = @intToPtr(*const u8, 0x1e);
|
|
||||||
pub const SYSTEM_FLAGS: *u8 = @intToPtr(*u8, 0x1f);
|
|
||||||
pub const FRAMEBUFFER: *[6400]u8 = @intToPtr(*[6400]u8, 0xA0);
|
|
||||||
|
|
||||||
pub const BUTTON_1: u8 = 1;
|
pub const MOUSE: *const Mouse = @intToPtr(*const Mouse, 0x1a);
|
||||||
pub const BUTTON_2: u8 = 2;
|
pub const SYSTEM_FLAGS: *SystemFlags = @intToPtr(*SystemFlags, 0x1f);
|
||||||
pub const BUTTON_LEFT: u8 = 16;
|
pub const FRAMEBUFFER: *[CANVAS_SIZE * CANVAS_SIZE / 4]u8 = @intToPtr(*[6400]u8, 0xA0);
|
||||||
pub const BUTTON_RIGHT: u8 = 32;
|
pub const ctx = Tex(.mut){
|
||||||
pub const BUTTON_UP: u8 = 64;
|
.data = @intToPtr([*]u8, 0xA0), // apparently casting *[N]u8 to [*]u8 at comptime causes a compiler crash
|
||||||
pub const BUTTON_DOWN: u8 = 128;
|
.size = .{ CANVAS_SIZE, CANVAS_SIZE },
|
||||||
|
};
|
||||||
|
|
||||||
pub const MOUSE_LEFT: u8 = 1;
|
pub const Gamepad = packed struct {
|
||||||
pub const MOUSE_RIGHT: u8 = 2;
|
button_1: bool,
|
||||||
pub const MOUSE_MIDDLE: u8 = 4;
|
button_2: bool,
|
||||||
|
_: u2 = 0,
|
||||||
|
button_left: bool,
|
||||||
|
button_right: bool,
|
||||||
|
button_up: bool,
|
||||||
|
button_down: bool,
|
||||||
|
comptime {
|
||||||
|
if (@sizeOf(@This()) != @sizeOf(u8)) unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(value: @This(), comptime _: []const u8, _: @import("std").fmt.FormatOptions, writer: anytype) !void {
|
||||||
|
if (value.button_1) try writer.writeAll("1");
|
||||||
|
if (value.button_2) try writer.writeAll("2");
|
||||||
|
if (value.button_left) try writer.writeAll("<"); //"←");
|
||||||
|
if (value.button_right) try writer.writeAll(">");
|
||||||
|
if (value.button_up) try writer.writeAll("^");
|
||||||
|
if (value.button_down) try writer.writeAll("v");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Mouse = packed struct {
|
||||||
|
x: i16,
|
||||||
|
y: i16,
|
||||||
|
buttons: MouseButtons,
|
||||||
|
pub fn pos(mouse: Mouse) Vec2 {
|
||||||
|
return .{ mouse.x, mouse.y };
|
||||||
|
}
|
||||||
|
comptime {
|
||||||
|
if (@sizeOf(@This()) != 5) unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MouseButtons = packed struct {
|
||||||
|
left: bool,
|
||||||
|
right: bool,
|
||||||
|
middle: bool,
|
||||||
|
_: u5 = 0,
|
||||||
|
comptime {
|
||||||
|
if (@sizeOf(@This()) != @sizeOf(u8)) unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SystemFlags = packed struct {
|
||||||
|
preserve_framebuffer: bool,
|
||||||
|
hide_gamepad_overlay: bool,
|
||||||
|
_: u6 = 0,
|
||||||
|
comptime {
|
||||||
|
if (@sizeOf(@This()) != @sizeOf(u8)) unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const SYSTEM_PRESERVE_FRAMEBUFFER: u8 = 1;
|
pub const SYSTEM_PRESERVE_FRAMEBUFFER: u8 = 1;
|
||||||
pub const SYSTEM_HIDE_GAMEPAD_OVERLAY: u8 = 2;
|
pub const SYSTEM_HIDE_GAMEPAD_OVERLAY: u8 = 2;
|
||||||
|
@ -47,38 +181,68 @@ pub const SYSTEM_HIDE_GAMEPAD_OVERLAY: u8 = 2;
|
||||||
// │ │
|
// │ │
|
||||||
// └───────────────────────────────────────────────────────────────────────────┘
|
// └───────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
/// Copies pixels to the framebuffer.
|
pub const externs = struct {
|
||||||
pub extern fn blit(sprite: [*]const u8, x: i32, y: i32, width: i32, height: i32, flags: u32) void;
|
pub extern fn blit(sprite: [*]const u8, x: i32, y: i32, width: i32, height: i32, flags: u32) void;
|
||||||
|
pub extern fn blitSub(sprite: [*]const u8, x: i32, y: i32, width: i32, height: i32, src_x: u32, src_y: u32, strie: i32, flags: u32) void;
|
||||||
/// Copies a subregion within a larger sprite atlas to the framebuffer.
|
|
||||||
pub extern fn blitSub(sprite: [*]const u8, x: i32, y: i32, width: i32, height: i32, src_x: u32, src_y: u32, stride: i32, flags: u32) void;
|
|
||||||
|
|
||||||
pub const BLIT_2BPP: u32 = 1;
|
|
||||||
pub const BLIT_1BPP: u32 = 0;
|
|
||||||
pub const BLIT_FLIP_X: u32 = 2;
|
|
||||||
pub const BLIT_FLIP_Y: u32 = 4;
|
|
||||||
pub const BLIT_ROTATE: u32 = 8;
|
|
||||||
|
|
||||||
/// Draws a line between two points.
|
|
||||||
pub extern fn line(x1: i32, y1: i32, x2: i32, y2: i32) void;
|
pub extern fn line(x1: i32, y1: i32, x2: i32, y2: i32) void;
|
||||||
|
|
||||||
/// Draws an oval (or circle).
|
|
||||||
pub extern fn oval(x: i32, y: i32, width: i32, height: i32) void;
|
pub extern fn oval(x: i32, y: i32, width: i32, height: i32) void;
|
||||||
|
pub extern fn rect(x: i32, y: i32, width: i32, height: i32) void;
|
||||||
/// Draws a rectangle.
|
pub extern fn textUtf8(strPtr: [*]const u8, strLen: usize, x: i32, y: i32) void;
|
||||||
pub extern fn rect(x: i32, y: i32, width: u32, height: u32) void;
|
|
||||||
|
|
||||||
/// Draws text using the built-in system font.
|
|
||||||
pub fn text(str: []const u8, x: i32, y: i32) void {
|
|
||||||
textUtf8(str.ptr, str.len, x, y);
|
|
||||||
}
|
|
||||||
extern fn textUtf8(strPtr: [*]const u8, strLen: usize, x: i32, y: i32) void;
|
|
||||||
|
|
||||||
/// Draws a vertical line
|
/// Draws a vertical line
|
||||||
pub extern fn vline(x: i32, y: i32, len: u32) void;
|
extern fn vline(x: i32, y: i32, len: u32) void;
|
||||||
|
|
||||||
/// Draws a horizontal line
|
/// Draws a horizontal line
|
||||||
pub extern fn hline(x: i32, y: i32, len: u32) void;
|
extern fn hline(x: i32, y: i32, len: u32) void;
|
||||||
|
|
||||||
|
pub extern fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Copies pixels to the framebuffer.
|
||||||
|
pub fn blit(sprite: []const u8, pos: Vec2, size: Vec2, flags: BlitFlags) void {
|
||||||
|
if (sprite.len * 8 != size[x] * size[y]) unreachable;
|
||||||
|
externs.blit(sprite.ptr, pos[x], pos[y], size[x], size[y], @bitCast(u32, flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copies a subregion within a larger sprite atlas to the framebuffer.
|
||||||
|
pub fn blitSub(sprite: []const u8, pos: Vec2, size: Vec2, src: Vec2, strie: i32, flags: BlitFlags) void {
|
||||||
|
if (sprite.len * 8 >= size[x] * size[y]) trace("Sprite not large enough {}", .{sprite.len});
|
||||||
|
externs.blitSub(sprite.ptr, pos[x], pos[y], size[x], size[y], @intCast(u32, src[x]), @intCast(u32, src[y]), strie, @bitCast(u32, flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const BlitFlags = packed struct {
|
||||||
|
bpp: enum(u1) {
|
||||||
|
b1,
|
||||||
|
b2,
|
||||||
|
},
|
||||||
|
flip_x: bool = false,
|
||||||
|
flip_y: bool = false,
|
||||||
|
rotate: bool = false,
|
||||||
|
_: u28 = 0,
|
||||||
|
comptime {
|
||||||
|
if (@sizeOf(@This()) != @sizeOf(u32)) unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Draws a line between two points.
|
||||||
|
pub fn line(pos1: Vec2, pos2: Vec2) void {
|
||||||
|
externs.line(pos1[x], pos1[y], pos2[x], pos2[y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws an oval (or circle).
|
||||||
|
pub fn oval(ul: Vec2, size: Vec2) void {
|
||||||
|
externs.oval(ul[x], ul[y], size[x], size[y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws a rectangle.
|
||||||
|
pub fn rect(ul: Vec2, size: Vec2) void {
|
||||||
|
externs.rect(ul[x], ul[y], size[x], size[y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws text using the built-in system font.
|
||||||
|
pub fn text(str: []const u8, pos: Vec2) void {
|
||||||
|
externs.textUtf8(str.ptr, str.len, pos[x], pos[y]);
|
||||||
|
}
|
||||||
|
|
||||||
// ┌───────────────────────────────────────────────────────────────────────────┐
|
// ┌───────────────────────────────────────────────────────────────────────────┐
|
||||||
// │ │
|
// │ │
|
||||||
|
@ -87,16 +251,51 @@ pub extern fn hline(x: i32, y: i32, len: u32) void;
|
||||||
// └───────────────────────────────────────────────────────────────────────────┘
|
// └───────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
/// Plays a sound tone.
|
/// Plays a sound tone.
|
||||||
pub extern fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) void;
|
pub fn tone(frequency: ToneFrequency, duration: ToneDuration, volume: u32, flags: ToneFlags) void {
|
||||||
|
return externs.tone(@bitCast(u32, frequency), @bitCast(u32, duration), volume, @bitCast(u8, flags));
|
||||||
|
}
|
||||||
|
pub const ToneFrequency = packed struct {
|
||||||
|
start: u16,
|
||||||
|
end: u16 = 0,
|
||||||
|
|
||||||
pub const TONE_PULSE1: u32 = 0;
|
comptime {
|
||||||
pub const TONE_PULSE2: u32 = 1;
|
if (@sizeOf(@This()) != @sizeOf(u32)) unreachable;
|
||||||
pub const TONE_TRIANGLE: u32 = 2;
|
}
|
||||||
pub const TONE_NOISE: u32 = 3;
|
};
|
||||||
pub const TONE_MODE1: u32 = 0;
|
|
||||||
pub const TONE_MODE2: u32 = 4;
|
pub const ToneDuration = packed struct {
|
||||||
pub const TONE_MODE3: u32 = 8;
|
sustain: u8 = 0,
|
||||||
pub const TONE_MODE4: u32 = 12;
|
release: u8 = 0,
|
||||||
|
decay: u8 = 0,
|
||||||
|
attack: u8 = 0,
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
if (@sizeOf(@This()) != @sizeOf(u32)) unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ToneFlags = packed struct {
|
||||||
|
pub const Channel = enum(u2) {
|
||||||
|
pulse1,
|
||||||
|
pulse2,
|
||||||
|
triangle,
|
||||||
|
noise,
|
||||||
|
};
|
||||||
|
pub const Mode = enum(u2) {
|
||||||
|
p12_5,
|
||||||
|
p25,
|
||||||
|
p50,
|
||||||
|
p75,
|
||||||
|
};
|
||||||
|
|
||||||
|
channel: Channel,
|
||||||
|
mode: Mode = .p12_5,
|
||||||
|
_: u4 = 0,
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
if (@sizeOf(@This()) != @sizeOf(u8)) unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ┌───────────────────────────────────────────────────────────────────────────┐
|
// ┌───────────────────────────────────────────────────────────────────────────┐
|
||||||
// │ │
|
// │ │
|
||||||
|
@ -117,10 +316,25 @@ pub extern fn diskw(src: [*]const u8, size: u32) u32;
|
||||||
// └───────────────────────────────────────────────────────────────────────────┘
|
// └───────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
/// Prints a message to the debug console.
|
/// Prints a message to the debug console.
|
||||||
pub fn trace(x: []const u8) void {
|
/// Disabled in release builds.
|
||||||
traceUtf8(x.ptr, x.len);
|
pub fn trace(comptime fmt: []const u8, args: anytype) void {
|
||||||
|
if (@import("builtin").mode != .Debug) @compileError("trace not allowed in release builds.");
|
||||||
|
|
||||||
|
// stack size is [8192]u8
|
||||||
|
var buffer: [100]u8 = undefined;
|
||||||
|
var fbs = std.io.fixedBufferStream(&buffer);
|
||||||
|
const writer = fbs.writer();
|
||||||
|
writer.print(fmt, args) catch {
|
||||||
|
const err_msg = switch (@import("builtin").mode) {
|
||||||
|
.Debug => "[trace err] " ++ fmt,
|
||||||
|
else => "[trace err]", // max 100 bytes in trace message.
|
||||||
|
};
|
||||||
|
return traceUtf8(err_msg, err_msg.len);
|
||||||
|
};
|
||||||
|
|
||||||
|
traceUtf8(&buffer, fbs.pos);
|
||||||
}
|
}
|
||||||
extern fn traceUtf8(strPtr: [*]const u8, strLen: usize) void;
|
extern fn traceUtf8(str_ptr: [*]const u8, str_len: usize) void;
|
||||||
|
|
||||||
/// Use with caution, as there's no compile-time type checking.
|
/// Use with caution, as there's no compile-time type checking.
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in New Issue