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 {
|
||||
const assets = std.build.Pkg{
|
||||
.name = "assets",
|
||||
.path = .{ .path = "assets/assets.zig" },
|
||||
};
|
||||
|
||||
const zig_version = @import("builtin").zig_version;
|
||||
const mode = b.standardReleaseOptions();
|
||||
const lib = b.addSharedLibrary("cart", "src/main.zig", .unversioned);
|
||||
lib.addPackage(assets);
|
||||
lib.setBuildMode(mode);
|
||||
lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
|
||||
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 ecs = @import("ecs.zig");
|
||||
const assets = @import("assets");
|
||||
|
||||
const smiley = [8]u8{
|
||||
0b11000011,
|
||||
0b10000001,
|
||||
0b00100100,
|
||||
0b00100100,
|
||||
0b00000000,
|
||||
0b00100100,
|
||||
0b10011001,
|
||||
0b11000011,
|
||||
const Vec2f = std.meta.Vector(2, f32);
|
||||
const Pos = Vec2f;
|
||||
const Control = enum { player };
|
||||
const Component = struct {
|
||||
pos: Pos,
|
||||
control: Control,
|
||||
};
|
||||
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 {
|
||||
w4.DRAW_COLORS.* = 2;
|
||||
w4.text("Hello from Zig!", 10, 10);
|
||||
w4.text("Hello from Zig!", .{ 10, 10 });
|
||||
|
||||
const gamepad = w4.GAMEPAD1.*;
|
||||
if (gamepad & w4.BUTTON_1 != 0) {
|
||||
if (w4.GAMEPAD1.button_1) {
|
||||
w4.DRAW_COLORS.* = 4;
|
||||
}
|
||||
|
||||
w4.blit(&smiley, 76, 76, 8, 8, w4.BLIT_1BPP);
|
||||
w4.text("Press X to blink", 16, 90);
|
||||
world.process(1, &.{ .pos, .control }, controlProcess);
|
||||
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", .{});
|
||||
}
|
||||
|
|
328
src/wasm4.zig
328
src/wasm4.zig
|
@ -1,13 +1,102 @@
|
|||
//
|
||||
// WASM-4: https://wasm4.org/docs
|
||||
//! Stolen from pfgithub's wasm4-zig repo
|
||||
//! https://github.com/pfgithub/wasm4-zig
|
||||
|
||||
// ┌───────────────────────────────────────────────────────────────────────────┐
|
||||
// │ │
|
||||
// │ Platform Constants │
|
||||
// │ │
|
||||
// └───────────────────────────────────────────────────────────────────────────┘
|
||||
const w4 = @This();
|
||||
const std = @import("std");
|
||||
|
||||
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 DRAW_COLORS: *u16 = @intToPtr(*u16, 0x14);
|
||||
pub const GAMEPAD1: *const u8 = @intToPtr(*const u8, 0x16);
|
||||
pub const GAMEPAD2: *const u8 = @intToPtr(*const u8, 0x17);
|
||||
pub const GAMEPAD3: *const u8 = @intToPtr(*const u8, 0x18);
|
||||
pub const GAMEPAD4: *const u8 = @intToPtr(*const u8, 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 GAMEPAD1: *const Gamepad = @intToPtr(*const Gamepad, 0x16);
|
||||
pub const GAMEPAD2: *const Gamepad = @intToPtr(*const Gamepad, 0x17);
|
||||
pub const GAMEPAD3: *const Gamepad = @intToPtr(*const Gamepad, 0x18);
|
||||
pub const GAMEPAD4: *const Gamepad = @intToPtr(*const Gamepad, 0x19);
|
||||
|
||||
pub const BUTTON_1: u8 = 1;
|
||||
pub const BUTTON_2: u8 = 2;
|
||||
pub const BUTTON_LEFT: u8 = 16;
|
||||
pub const BUTTON_RIGHT: u8 = 32;
|
||||
pub const BUTTON_UP: u8 = 64;
|
||||
pub const BUTTON_DOWN: u8 = 128;
|
||||
pub const MOUSE: *const Mouse = @intToPtr(*const Mouse, 0x1a);
|
||||
pub const SYSTEM_FLAGS: *SystemFlags = @intToPtr(*SystemFlags, 0x1f);
|
||||
pub const FRAMEBUFFER: *[CANVAS_SIZE * CANVAS_SIZE / 4]u8 = @intToPtr(*[6400]u8, 0xA0);
|
||||
pub const ctx = Tex(.mut){
|
||||
.data = @intToPtr([*]u8, 0xA0), // apparently casting *[N]u8 to [*]u8 at comptime causes a compiler crash
|
||||
.size = .{ CANVAS_SIZE, CANVAS_SIZE },
|
||||
};
|
||||
|
||||
pub const MOUSE_LEFT: u8 = 1;
|
||||
pub const MOUSE_RIGHT: u8 = 2;
|
||||
pub const MOUSE_MIDDLE: u8 = 4;
|
||||
pub const Gamepad = packed struct {
|
||||
button_1: bool,
|
||||
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_HIDE_GAMEPAD_OVERLAY: u8 = 2;
|
||||
|
@ -47,38 +181,68 @@ pub const SYSTEM_HIDE_GAMEPAD_OVERLAY: u8 = 2;
|
|||
// │ │
|
||||
// └───────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
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 blitSub(sprite: [*]const u8, x: i32, y: i32, width: i32, height: i32, src_x: u32, src_y: u32, strie: i32, flags: u32) void;
|
||||
pub extern fn line(x1: i32, y1: i32, x2: i32, y2: 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;
|
||||
pub extern fn textUtf8(strPtr: [*]const u8, strLen: usize, x: i32, y: i32) void;
|
||||
|
||||
/// Draws a vertical line
|
||||
extern fn vline(x: i32, y: i32, len: u32) void;
|
||||
|
||||
/// Draws a horizontal line
|
||||
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 extern fn blit(sprite: [*]const u8, x: i32, y: i32, width: i32, height: i32, flags: u32) void;
|
||||
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 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 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 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;
|
||||
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 extern fn line(x1: i32, y1: i32, x2: i32, y2: i32) void;
|
||||
pub fn line(pos1: Vec2, pos2: Vec2) void {
|
||||
externs.line(pos1[x], pos1[y], pos2[x], pos2[y]);
|
||||
}
|
||||
|
||||
/// Draws an oval (or circle).
|
||||
pub extern fn oval(x: i32, y: i32, width: i32, height: i32) void;
|
||||
pub fn oval(ul: Vec2, size: Vec2) void {
|
||||
externs.oval(ul[x], ul[y], size[x], size[y]);
|
||||
}
|
||||
|
||||
/// Draws a rectangle.
|
||||
pub extern fn rect(x: i32, y: i32, width: u32, height: u32) void;
|
||||
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, x: i32, y: i32) void {
|
||||
textUtf8(str.ptr, str.len, x, y);
|
||||
pub fn text(str: []const u8, pos: Vec2) void {
|
||||
externs.textUtf8(str.ptr, str.len, pos[x], pos[y]);
|
||||
}
|
||||
extern fn textUtf8(strPtr: [*]const u8, strLen: usize, x: i32, y: i32) void;
|
||||
|
||||
/// Draws a vertical line
|
||||
pub extern fn vline(x: i32, y: i32, len: u32) void;
|
||||
|
||||
/// Draws a horizontal line
|
||||
pub extern fn hline(x: i32, y: i32, len: u32) void;
|
||||
|
||||
// ┌───────────────────────────────────────────────────────────────────────────┐
|
||||
// │ │
|
||||
|
@ -87,16 +251,51 @@ pub extern fn hline(x: i32, y: i32, len: u32) void;
|
|||
// └───────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
/// 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;
|
||||
pub const TONE_PULSE2: u32 = 1;
|
||||
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 TONE_MODE3: u32 = 8;
|
||||
pub const TONE_MODE4: u32 = 12;
|
||||
comptime {
|
||||
if (@sizeOf(@This()) != @sizeOf(u32)) unreachable;
|
||||
}
|
||||
};
|
||||
|
||||
pub const ToneDuration = packed struct {
|
||||
sustain: u8 = 0,
|
||||
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.
|
||||
pub fn trace(x: []const u8) void {
|
||||
traceUtf8(x.ptr, x.len);
|
||||
/// Disabled in release builds.
|
||||
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.
|
||||
///
|
||||
|
|
Loading…
Reference in New Issue