add switching to simulated mode in gui (freezes on exitting)
This commit is contained in:
parent
b6859909ab
commit
1cca8c5806
@ -20,6 +20,10 @@ pub fn subtract(self: Position, other: Position) Position {
|
||||
return init(self.x - other.x, self.y - other.y);
|
||||
}
|
||||
|
||||
pub fn multiply(self: Position, other: Position) Position {
|
||||
return init(self.x * other.x, self.y * other.y);
|
||||
}
|
||||
|
||||
pub fn distance(self: Position, other: Position) f32 {
|
||||
const dx: f32 = @floatFromInt(self.x - other.x);
|
||||
const dy: f32 = @floatFromInt(self.y - other.y);
|
||||
|
@ -459,6 +459,14 @@ pub fn prefetch(self: *Server, allocator: std.mem.Allocator, opts: PrefetchOptio
|
||||
_ = try self.getImage(.map, skin);
|
||||
}
|
||||
}
|
||||
|
||||
inline for (std.meta.fields(Character.Skin)) |field| {
|
||||
const skin: Character.Skin = @enumFromInt(field.value);
|
||||
const skin_name = skin.toString();
|
||||
if (self.store.images.getId(.character, skin_name) == null) {
|
||||
_ = try self.getImage(.character, skin_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
496
gui/app.zig
496
gui/app.zig
@ -12,6 +12,7 @@ const rlgl_h = @cImport({
|
||||
});
|
||||
|
||||
const assert = std.debug.assert;
|
||||
const log = std.log.scoped(.app);
|
||||
|
||||
const App = @This();
|
||||
|
||||
@ -27,6 +28,7 @@ map_textures: std.ArrayList(MapTexture),
|
||||
map_texture_indexes: std.ArrayList(usize),
|
||||
map_position_min: Api.Position,
|
||||
map_position_max: Api.Position,
|
||||
character_skin_textures: std.EnumArray(Api.Character.Skin, rl.Texture2D),
|
||||
camera: rl.Camera2D,
|
||||
font_face: FontFace,
|
||||
|
||||
@ -35,7 +37,20 @@ blur_texture_horizontal: ?rl.RenderTexture = null,
|
||||
blur_texture_both: ?rl.RenderTexture = null,
|
||||
blur_shader: rl.Shader,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, store: *Api.Store, server: *Api.Server) !App {
|
||||
simulation: bool = false,
|
||||
|
||||
system_clock: Artificer.SystemClock,
|
||||
started_at: i128,
|
||||
artificer: Artificer.ArtificerApi,
|
||||
sim_artificer: Artificer.ArtificerSim,
|
||||
sim_server: Artificer.SimServer,
|
||||
sim_started_at: i128,
|
||||
last_sim_timestamp: i128 = 0,
|
||||
|
||||
thread_running: bool = true,
|
||||
artificer_thread: std.Thread,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, store: *Api.Store, server: *Api.Server) !*App {
|
||||
var map_textures = std.ArrayList(MapTexture).init(allocator);
|
||||
errdefer map_textures.deinit();
|
||||
errdefer {
|
||||
@ -54,20 +69,10 @@ pub fn init(allocator: std.mem.Allocator, store: *Api.Store, server: *Api.Server
|
||||
|
||||
for (map_image_ids) |image_id| {
|
||||
const image = store.images.get(image_id).?;
|
||||
const texture = rl.loadTextureFromImage(rl.Image{
|
||||
.width = @intCast(image.width),
|
||||
.height = @intCast(image.height),
|
||||
.data = store.images.getRGBA(image_id).?.ptr,
|
||||
.mipmaps = 1,
|
||||
.format = rl.PixelFormat.pixelformat_uncompressed_r8g8b8a8
|
||||
});
|
||||
if (!rl.isTextureReady(texture)) {
|
||||
return error.LoadMapTextureFromImage;
|
||||
}
|
||||
|
||||
map_textures.appendAssumeCapacity(MapTexture{
|
||||
.name = try Api.Map.Skin.fromSlice(image.code.slice()),
|
||||
.texture = texture
|
||||
.texture = try loadTextureFromStore(store, image_id)
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -107,6 +112,15 @@ pub fn init(allocator: std.mem.Allocator, store: *Api.Store, server: *Api.Server
|
||||
}
|
||||
}
|
||||
|
||||
var character_skin_textures = std.EnumArray(Api.Character.Skin, rl.Texture2D).initUndefined();
|
||||
|
||||
inline for (std.meta.fields(Api.Character.Skin)) |field| {
|
||||
const skin: Api.Character.Skin = @enumFromInt(field.value);
|
||||
const skin_image_id = store.images.getId(.character, skin.toString()).?;
|
||||
const texture = try loadTextureFromStore(store, skin_image_id);
|
||||
character_skin_textures.set(skin, texture);
|
||||
}
|
||||
|
||||
const blur_shader = rl.loadShaderFromMemory(
|
||||
@embedFile("./base.vsh"),
|
||||
@embedFile("./blur.fsh"),
|
||||
@ -124,29 +138,75 @@ pub fn init(allocator: std.mem.Allocator, store: *Api.Store, server: *Api.Server
|
||||
return error.LoadFontFromMemory;
|
||||
}
|
||||
|
||||
return App{
|
||||
const font_face = FontFace{ .font = font };
|
||||
|
||||
const ui = UI.init(font_face);
|
||||
|
||||
const character_id = store.characters.getId("Blondie").?;
|
||||
|
||||
const sim_server = Artificer.SimServer.init(0, store);
|
||||
|
||||
var app = try allocator.create(App);
|
||||
errdefer allocator.destroy(app);
|
||||
|
||||
app.* = App{
|
||||
.store = store,
|
||||
.server = server,
|
||||
.ui = UI.init(),
|
||||
.system_clock = .{},
|
||||
.sim_server = sim_server,
|
||||
.started_at = 0,
|
||||
.sim_started_at = sim_server.clock.nanoTimestamp(),
|
||||
.ui = ui,
|
||||
.map_textures = map_textures,
|
||||
.map_texture_indexes = map_texture_indexes,
|
||||
.map_position_max = map_position_max,
|
||||
.map_position_min = map_position_min,
|
||||
.character_skin_textures = character_skin_textures,
|
||||
.blur_shader = blur_shader,
|
||||
.font_face = .{ .font = font },
|
||||
.font_face = font_face,
|
||||
.camera = rl.Camera2D{
|
||||
.offset = rl.Vector2.zero(),
|
||||
.target = rl.Vector2.zero(),
|
||||
.rotation = 0,
|
||||
.zoom = 1,
|
||||
}
|
||||
},
|
||||
|
||||
.artificer = undefined,
|
||||
.sim_artificer = undefined,
|
||||
.artificer_thread = undefined,
|
||||
};
|
||||
app.started_at = app.system_clock.nanoTimestamp();
|
||||
|
||||
app.sim_artificer = try Artificer.ArtificerSim.init(allocator, store, &app.sim_server.clock, &app.sim_server, character_id);
|
||||
errdefer app.sim_artificer.deinit(allocator);
|
||||
|
||||
app.artificer = try Artificer.ArtificerApi.init(allocator, store, &app.system_clock, server, character_id);
|
||||
errdefer app.artificer.deinit(allocator);
|
||||
|
||||
app.artificer_thread = try std.Thread.spawn(.{ .allocator = allocator }, artificer_thread_cb, .{ app });
|
||||
errdefer {
|
||||
app.thread_running = false;
|
||||
app.artificer_thread.join();
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *App) void {
|
||||
const allocator = self.map_textures.allocator;
|
||||
|
||||
self.thread_running = false;
|
||||
self.artificer_thread.join();
|
||||
|
||||
self.artificer.deinit(allocator);
|
||||
self.sim_artificer.deinit(allocator);
|
||||
|
||||
for (self.map_textures.items) |map_texture| {
|
||||
map_texture.texture.unload();
|
||||
}
|
||||
for (self.character_skin_textures.values) |texture| {
|
||||
texture.unload();
|
||||
}
|
||||
|
||||
self.map_textures.deinit();
|
||||
self.map_texture_indexes.deinit();
|
||||
@ -161,6 +221,55 @@ pub fn deinit(self: *App) void {
|
||||
if (self.blur_texture_original) |render_texture| {
|
||||
render_texture.unload();
|
||||
}
|
||||
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
fn artificer_thread_cb(app: *App) void {
|
||||
while (app.thread_running) {
|
||||
|
||||
if (app.simulation) {
|
||||
const artificer = &app.sim_artificer;
|
||||
|
||||
const expires_in = artificer.timeUntilCooldownExpires();
|
||||
if (expires_in > 0) {
|
||||
artificer.clock.sleep(expires_in);
|
||||
}
|
||||
|
||||
artificer.tick() catch |e| {
|
||||
log.err("Error in .tick in thread: {}", .{e});
|
||||
app.thread_running = false;
|
||||
};
|
||||
} else {
|
||||
const artificer = &app.artificer;
|
||||
|
||||
const expires_in = artificer.timeUntilCooldownExpires();
|
||||
if (expires_in > 0) {
|
||||
artificer.clock.sleep(expires_in);
|
||||
}
|
||||
|
||||
artificer.tick() catch |e| {
|
||||
log.err("Error in .tick in thread: {}", .{e});
|
||||
app.thread_running = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn loadTextureFromStore(store: *Api.Store, image_id: Api.Store.Id) !rl.Texture2D {
|
||||
const image = store.images.get(image_id).?;
|
||||
const texture = rl.loadTextureFromImage(rl.Image{
|
||||
.width = @intCast(image.width),
|
||||
.height = @intCast(image.height),
|
||||
.data = store.images.getRGBA(image_id).?.ptr,
|
||||
.mipmaps = 1,
|
||||
.format = rl.PixelFormat.pixelformat_uncompressed_r8g8b8a8
|
||||
});
|
||||
if (!rl.isTextureReady(texture)) {
|
||||
return error.LoadMapTextureFromImage;
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
fn cameraControls(camera: *rl.Camera2D) void {
|
||||
@ -243,206 +352,10 @@ fn createOrGetRenderTexture(maybe_render_texture: *?rl.RenderTexture) !rl.Render
|
||||
return maybe_render_texture.*.?;
|
||||
}
|
||||
|
||||
// Modified version of `DrawRectangleRounded` where the UV texture coordiantes are consistent and align
|
||||
fn drawRectangleRoundedUV(rec: rl.Rectangle, roundness: f32, color: rl.Color) void {
|
||||
assert(roundness < 1);
|
||||
|
||||
if (roundness <= 0 or rec.width <= 1 or rec.height <= 1) {
|
||||
rl.drawRectangleRec(rec, color);
|
||||
return;
|
||||
}
|
||||
|
||||
const radius: f32 = @min(rec.width, rec.height) * roundness / 2;
|
||||
if (radius <= 0.0) return;
|
||||
|
||||
// Calculate the maximum angle between segments based on the error rate (usually 0.5f)
|
||||
const smooth_circle_error_rate = 0.5;
|
||||
const th: f32 = std.math.acos(2 * std.math.pow(f32, 1 - smooth_circle_error_rate / radius, 2) - 1);
|
||||
var segments: i32 = @intFromFloat(@ceil(2 * std.math.pi / th) / 4.0);
|
||||
if (segments <= 0) segments = 4;
|
||||
|
||||
const step_length = 90.0 / @as(f32, @floatFromInt(segments));
|
||||
|
||||
// Quick sketch to make sense of all of this,
|
||||
// there are 9 parts to draw, also mark the 12 points we'll use
|
||||
//
|
||||
// P0____________________P1
|
||||
// /| |\
|
||||
// /1| 2 |3\
|
||||
// P7 /__|____________________|__\ P2
|
||||
// | |P8 P9| |
|
||||
// | 8 | 9 | 4 |
|
||||
// | __|____________________|__ |
|
||||
// P6 \ |P11 P10| / P3
|
||||
// \7| 6 |5/
|
||||
// \|____________________|/
|
||||
// P5 P4
|
||||
|
||||
// Coordinates of the 12 points that define the rounded rect
|
||||
const radius_u = radius / rec.width;
|
||||
const radius_v = radius / rec.height;
|
||||
const points = [_]rl.Vector2{
|
||||
.{ .x = radius_u , .y = 0 }, // P0
|
||||
.{ .x = 1 - radius_u , .y = 0 }, // P1
|
||||
.{ .x = 1 , .y = radius_v }, // P2
|
||||
.{ .x = 1 , .y = 1 - radius_v }, // P3
|
||||
.{ .x = 1 - radius_u , .y = 1 }, // P4
|
||||
.{ .x = radius_u , .y = 1 }, // P5
|
||||
.{ .x = 0 , .y = 1 - radius_v }, // P6
|
||||
.{ .x = 0 , .y = radius_v }, // P7
|
||||
.{ .x = radius_u , .y = radius_v }, // P8
|
||||
.{ .x = 1 - radius_u , .y = radius_v }, // P9
|
||||
.{ .x = 1 - radius_u , .y = 1 - radius_v }, // P10
|
||||
.{ .x = radius_u , .y = 1 - radius_v }, // P11
|
||||
};
|
||||
|
||||
const texture = rl.getShapesTexture();
|
||||
const shape_rect = rl.getShapesTextureRectangle();
|
||||
|
||||
const texture_width: f32 = @floatFromInt(texture.width);
|
||||
const texture_height: f32 = @floatFromInt(texture.height);
|
||||
|
||||
rl.gl.rlBegin(rlgl_h.RL_TRIANGLES);
|
||||
defer rl.gl.rlEnd();
|
||||
|
||||
rl.gl.rlSetTexture(texture.id);
|
||||
defer rl.gl.rlSetTexture(0);
|
||||
|
||||
// Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner
|
||||
const centers = [_]rl.Vector2{ points[8], points[9], points[10], points[11] };
|
||||
const angles = [_]f32{ 180.0, 270.0, 0.0, 90.0 };
|
||||
for (0..4) |k| {
|
||||
var angle = angles[k];
|
||||
const center = centers[k];
|
||||
for (0..@intCast(segments)) |_| {
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
|
||||
const rad_per_deg = std.math.rad_per_deg;
|
||||
|
||||
const triangle = .{
|
||||
center,
|
||||
.{
|
||||
.x = center.x + @cos(rad_per_deg*(angle + step_length))*radius_u,
|
||||
.y = center.y + @sin(rad_per_deg*(angle + step_length))*radius_v
|
||||
},
|
||||
.{
|
||||
.x = center.x + @cos(rad_per_deg * angle)*radius_u,
|
||||
.y = center.y + @sin(rad_per_deg * angle)*radius_v
|
||||
}
|
||||
};
|
||||
|
||||
inline for (triangle) |point| {
|
||||
rl.gl.rlTexCoord2f(
|
||||
(shape_rect.x + shape_rect.width * point.x) / texture_width,
|
||||
(shape_rect.y + shape_rect.height * point.y) / texture_height
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + rec.width * point.x,
|
||||
rec.y + rec.height * point.y
|
||||
);
|
||||
}
|
||||
|
||||
angle += step_length;
|
||||
}
|
||||
}
|
||||
|
||||
// [2] Upper Rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 0, 8, 9, 1, 0, 9 }) |index| {
|
||||
const point = points[index];
|
||||
rl.gl.rlTexCoord2f(
|
||||
(shape_rect.x + shape_rect.width * point.x) / texture_width,
|
||||
(shape_rect.y + shape_rect.height * point.y) / texture_height
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + rec.width * point.x,
|
||||
rec.y + rec.height * point.y
|
||||
);
|
||||
}
|
||||
|
||||
// [4] Right Rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 9, 10, 3, 2, 9, 3 }) |index| {
|
||||
const point = points[index];
|
||||
rl.gl.rlTexCoord2f(
|
||||
(shape_rect.x + shape_rect.width * point.x) / texture_width,
|
||||
(shape_rect.y + shape_rect.height * point.y) / texture_height
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + rec.width * point.x,
|
||||
rec.y + rec.height * point.y
|
||||
);
|
||||
}
|
||||
|
||||
// [6] Bottom Rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 11, 5, 4, 10, 11, 4 }) |index| {
|
||||
const point = points[index];
|
||||
rl.gl.rlTexCoord2f(
|
||||
(shape_rect.x + shape_rect.width * point.x) / texture_width,
|
||||
(shape_rect.y + shape_rect.height * point.y) / texture_height
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + rec.width * point.x,
|
||||
rec.y + rec.height * point.y
|
||||
);
|
||||
}
|
||||
|
||||
// [8] Left Rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 7, 6, 11, 8, 7, 11 }) |index| {
|
||||
const point = points[index];
|
||||
rl.gl.rlTexCoord2f(
|
||||
(shape_rect.x + shape_rect.width * point.x) / texture_width,
|
||||
(shape_rect.y + shape_rect.height * point.y) / texture_height
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + rec.width * point.x,
|
||||
rec.y + rec.height * point.y
|
||||
);
|
||||
}
|
||||
|
||||
// [9] Middle Rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 8, 11, 10, 9, 8, 10 }) |index| {
|
||||
const point = points[index];
|
||||
rl.gl.rlTexCoord2f(
|
||||
(shape_rect.x + shape_rect.width * point.x) / texture_width,
|
||||
(shape_rect.y + shape_rect.height * point.y) / texture_height
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + rec.width * point.x,
|
||||
rec.y + rec.height * point.y
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn drawBlurredWorld(self: *App, rect: rl.Rectangle, color: rl.Color) void {
|
||||
const blur_both = self.blur_texture_both.?.texture;
|
||||
|
||||
const previous_texture = rl.getShapesTexture();
|
||||
const previous_rect = rl.getShapesTextureRectangle();
|
||||
defer rl.setShapesTexture(previous_texture, previous_rect);
|
||||
|
||||
const texture_height: f32 = @floatFromInt(blur_both.height);
|
||||
const shape_rect = rl.Rectangle{
|
||||
.x = rect.x,
|
||||
.y = texture_height - rect.y,
|
||||
.width = rect.width,
|
||||
.height = -rect.height,
|
||||
};
|
||||
rl.setShapesTexture(blur_both, shape_rect);
|
||||
|
||||
const border = 2;
|
||||
const roundness = 0.2;
|
||||
drawRectangleRoundedUV(rect, roundness, color);
|
||||
rl.drawRectangleRoundedLinesEx(RectUtils.shrink(rect, border - 1, border - 1), roundness, 0, border, srcery.bright_white.alpha(0.3));
|
||||
}
|
||||
|
||||
pub fn drawWorld(self: *App) void {
|
||||
rl.clearBackground(srcery.black);
|
||||
|
||||
rl.drawCircleV(rl.Vector2.zero(), 5, rl.Color.red);
|
||||
const tile_size = rl.Vector2.init(224, 224);
|
||||
|
||||
const map_size = self.map_position_max.subtract(self.map_position_min);
|
||||
for (0..@intCast(map_size.y)) |oy| {
|
||||
@ -453,11 +366,32 @@ pub fn drawWorld(self: *App) void {
|
||||
const texture_index = self.map_texture_indexes.items[map_index];
|
||||
const texture = self.map_textures.items[texture_index].texture;
|
||||
|
||||
const tile_size = rl.Vector2.init(224, 224);
|
||||
const position = rl.Vector2.init(@floatFromInt(x), @floatFromInt(y)).multiply(tile_size);
|
||||
rl.drawTextureV(texture, position, rl.Color.white);
|
||||
}
|
||||
}
|
||||
|
||||
for (self.store.characters.objects.items) |optional_character| {
|
||||
if (optional_character != .object) continue;
|
||||
const character = optional_character.object;
|
||||
const skin_texture = self.character_skin_textures.get(character.skin);
|
||||
|
||||
const position = rl.Vector2{
|
||||
.x = @floatFromInt(character.position.x),
|
||||
.y = @floatFromInt(character.position.y),
|
||||
};
|
||||
|
||||
const skin_size = rl.Vector2{
|
||||
.x = @floatFromInt(skin_texture.width),
|
||||
.y = @floatFromInt(skin_texture.height),
|
||||
};
|
||||
|
||||
rl.drawTextureV(
|
||||
skin_texture,
|
||||
position.addValue(0.5).multiply(tile_size).subtract(skin_size.divide(.{ .x = 2, .y = 2 })),
|
||||
rl.Color.white
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawWorldAndBlur(self: *App) !void {
|
||||
@ -490,7 +424,6 @@ pub fn drawWorldAndBlur(self: *App) !void {
|
||||
const i_i32: i32 = @intCast(i);
|
||||
const io: f32 = @floatFromInt(i_i32 - kernel_radius);
|
||||
kernel_coeffs[i] = @exp(-(io * io) / (sigma * sigma));
|
||||
// kernel_coeffs[i] /= @floatFromInt(kernel_coeffs.len);
|
||||
}
|
||||
|
||||
var kernel_sum: f32 = 0;
|
||||
@ -601,11 +534,6 @@ fn drawRatelimits(self: *App, box: rl.Rectangle) void {
|
||||
const Category = Api.RateLimit.Category;
|
||||
const ratelimits = self.server.ratelimits;
|
||||
|
||||
self.drawBlurredWorld(
|
||||
box,
|
||||
srcery.xgray10
|
||||
);
|
||||
|
||||
const padding = 16;
|
||||
var stack = UI.Stack.init(RectUtils.shrink(box, padding, padding), .top_to_bottom);
|
||||
stack.gap = 8;
|
||||
@ -663,16 +591,92 @@ fn drawRatelimits(self: *App, box: rl.Rectangle) void {
|
||||
}
|
||||
}
|
||||
|
||||
fn showButton(self: *App, text: []const u8) UI.Interaction {
|
||||
const key_hash: u16 = @truncate(@intFromPtr(text.ptr));
|
||||
var button = self.ui.getOrAppendWidget(key_hash);
|
||||
button.padding.vertical(8);
|
||||
button.padding.horizontal(16);
|
||||
|
||||
button.flags.insert(.clickable);
|
||||
button.size = .{
|
||||
.x = .{ .text = {} },
|
||||
.y = .{ .text = {} },
|
||||
};
|
||||
|
||||
const interaction = self.ui.getInteraction(button);
|
||||
var text_color: rl.Color = undefined;
|
||||
if (interaction.held_down) {
|
||||
button.background = .{ .color = srcery.hard_black };
|
||||
text_color = srcery.white;
|
||||
} else if (interaction.hovering) {
|
||||
button.background = .{ .color = srcery.bright_black };
|
||||
text_color = srcery.bright_white;
|
||||
} else {
|
||||
button.background = .{ .color = srcery.black };
|
||||
text_color = srcery.bright_white;
|
||||
}
|
||||
|
||||
button.text = .{
|
||||
.content = text,
|
||||
.color = text_color
|
||||
};
|
||||
|
||||
return interaction;
|
||||
}
|
||||
|
||||
fn showLabel(self: *App, text: []const u8) UI.Widget.Key {
|
||||
const key_hash: u16 = @truncate(@intFromPtr(text.ptr));
|
||||
var label = self.ui.getOrAppendWidget(key_hash);
|
||||
|
||||
label.text = .{
|
||||
.content = text
|
||||
};
|
||||
label.size = .{
|
||||
.x = .{ .text = {} },
|
||||
.y = .{ .text = {} }
|
||||
};
|
||||
return label.key;
|
||||
}
|
||||
|
||||
pub fn toggleSimulationMode(self: *App) void {
|
||||
self.simulation = !self.simulation;
|
||||
|
||||
if (self.simulation) {
|
||||
const system_clock = self.system_clock;
|
||||
const time_passed = system_clock.nanoTimestamp() - self.started_at;
|
||||
|
||||
const sim_clock = &self.sim_server.clock;
|
||||
sim_clock.timestamp_limit = self.sim_started_at + time_passed;
|
||||
sim_clock.timestamp = sim_clock.timestamp_limit.?;
|
||||
self.last_sim_timestamp = std.time.nanoTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(self: *App) !void {
|
||||
for (&self.server.ratelimits.values) |*ratelimit| {
|
||||
ratelimit.update_timers(std.time.milliTimestamp());
|
||||
}
|
||||
|
||||
if (self.simulation) {
|
||||
const now = std.time.nanoTimestamp();
|
||||
const time_passed = now - self.last_sim_timestamp;
|
||||
self.last_sim_timestamp = now;
|
||||
|
||||
|
||||
const sim_clock = &self.sim_server.clock;
|
||||
sim_clock.timestamp_limit.? += time_passed;
|
||||
}
|
||||
|
||||
const screen_width = rl.getScreenWidth();
|
||||
const screen_height = rl.getScreenHeight();
|
||||
const screen_size = rl.Vector2.init(@floatFromInt(screen_width), @floatFromInt(screen_height));
|
||||
|
||||
if (!self.ui.isHoveringAnything()) {
|
||||
cameraControls(&self.camera);
|
||||
self.ui.disable_mouse_interaction = rl.isMouseButtonDown(.mouse_button_left);
|
||||
} else {
|
||||
self.ui.disable_mouse_interaction = false;
|
||||
}
|
||||
|
||||
rl.beginDrawing();
|
||||
defer rl.endDrawing();
|
||||
@ -681,9 +685,55 @@ pub fn tick(self: *App) !void {
|
||||
|
||||
try self.drawWorldAndBlur();
|
||||
|
||||
self.drawRatelimits(
|
||||
.{ .x = 20, .y = 20, .width = 200, .height = 200 },
|
||||
);
|
||||
const ui = &self.ui;
|
||||
if (self.blur_texture_both) |blur_texture_both| {
|
||||
ui.blur_background = blur_texture_both.texture;
|
||||
}
|
||||
ui.begin();
|
||||
|
||||
ui.topWidget().padding.all(16);
|
||||
ui.layout().gap = 8;
|
||||
|
||||
{
|
||||
const panel = ui.getOrAppendWidget(ui.randomWidgetHash());
|
||||
panel.size = .{ .x = .fit_children, .y = .fit_children };
|
||||
panel.padding.all(16);
|
||||
panel.layout.gap = 8;
|
||||
panel.background = .blur_world;
|
||||
ui.pushWidget(panel.key);
|
||||
defer ui.popWidget();
|
||||
|
||||
if (self.simulation) {
|
||||
const clock = self.sim_server.clock;
|
||||
const started_at = self.sim_started_at;
|
||||
const time_passed: f32 = @floatFromInt(clock.nanoTimestamp() - started_at);
|
||||
var time_passed_buff: [128]u8 = undefined;
|
||||
const time_passed_str = try std.fmt.bufPrint(&time_passed_buff, "Time passed: {d:.1}s", .{ time_passed / std.time.ns_per_s });
|
||||
|
||||
_ = self.showLabel(time_passed_str);
|
||||
if (self.showButton("Turn off simulation").clicked) {
|
||||
self.toggleSimulationMode();
|
||||
}
|
||||
} else {
|
||||
const clock = self.system_clock;
|
||||
const started_at = self.started_at;
|
||||
const time_passed: f32 = @floatFromInt(clock.nanoTimestamp() - started_at);
|
||||
var time_passed_buff: [128]u8 = undefined;
|
||||
const time_passed_str = try std.fmt.bufPrint(&time_passed_buff, "Time passed: {d:.1}s", .{ time_passed / std.time.ns_per_s });
|
||||
|
||||
_ = self.showLabel(time_passed_str);
|
||||
if (self.showButton("Turn on simulation").clicked) {
|
||||
self.toggleSimulationMode();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ui.end();
|
||||
|
||||
// self.drawRatelimits(
|
||||
// .{ .x = 20, .y = 20, .width = 200, .height = 200 },
|
||||
// );
|
||||
|
||||
rl.drawFPS(
|
||||
@as(i32, @intFromFloat(screen_size.x)) - 100,
|
||||
|
@ -104,6 +104,9 @@ pub fn main() anyerror!void {
|
||||
try server.prefetchCached(allocator, cache_path, .{ .images = true });
|
||||
}
|
||||
|
||||
const characters = try server.getMyCharacters(allocator);
|
||||
characters.deinit();
|
||||
|
||||
rl.initWindow(800, 450, "Artificer");
|
||||
defer rl.closeWindow();
|
||||
|
||||
|
940
gui/ui.zig
940
gui/ui.zig
@ -1,200 +1,854 @@
|
||||
// zig fmt: off
|
||||
const std = @import("std");
|
||||
const rl = @import("raylib");
|
||||
const rect_utils = @import("./rect-utils.zig");
|
||||
const assert = std.debug.assert;
|
||||
const SourceLocation = std.builtin.SourceLocation;
|
||||
const FontFace = @import("./font-face.zig");
|
||||
const builtin = @import("builtin");
|
||||
const srcery = @import("./srcery.zig");
|
||||
const rlgl_h = @cImport({
|
||||
@cInclude("rlgl.h");
|
||||
});
|
||||
|
||||
// TODO: Implement Id context (I.e. ID parenting)
|
||||
const Rect = rl.Rectangle;
|
||||
const Vec2 = rl.Vector2;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const UI = @This();
|
||||
|
||||
const max_stack_depth = 16;
|
||||
const TransformFrame = struct {
|
||||
offset: rl.Vector2,
|
||||
scale: rl.Vector2,
|
||||
pub const Interaction = struct {
|
||||
widget: Widget.Key,
|
||||
|
||||
clicked: bool = false,
|
||||
|
||||
hovering: bool = false,
|
||||
pressed: bool = false,
|
||||
released: bool = false,
|
||||
held_down: bool = false,
|
||||
};
|
||||
|
||||
hot_widget: ?Id = null,
|
||||
active_widget: ?Id = null,
|
||||
const SemanticSize = union(enum) {
|
||||
pixels: f32,
|
||||
percent: f32,
|
||||
fit_children,
|
||||
text
|
||||
};
|
||||
|
||||
transform_stack: std.BoundedArray(TransformFrame, max_stack_depth),
|
||||
const SemanticVec2 = struct {
|
||||
x: SemanticSize,
|
||||
y: SemanticSize,
|
||||
};
|
||||
|
||||
pub fn init() UI {
|
||||
var stack = std.BoundedArray(TransformFrame, max_stack_depth).init(0) catch unreachable;
|
||||
stack.appendAssumeCapacity(TransformFrame{
|
||||
.offset = rl.Vector2{ .x = 0, .y = 0 },
|
||||
.scale = rl.Vector2{ .x = 1, .y = 1 },
|
||||
});
|
||||
pub const Widget = struct {
|
||||
const Flag = enum {
|
||||
clickable,
|
||||
passthrough
|
||||
};
|
||||
|
||||
const Flags = std.EnumSet(Flag);
|
||||
|
||||
pub const Key = packed struct {
|
||||
hash: u16 = 0,
|
||||
extra: u16 = 0,
|
||||
|
||||
pub fn fromUsize(number: usize) Key {
|
||||
return .{
|
||||
.hash = @truncate(number),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn eql(self: Key, other: Key) bool {
|
||||
return self.hash == other.hash and self.extra == other.extra;
|
||||
}
|
||||
|
||||
pub fn indexOf(haystack: []const Key, needle: Key) ?usize {
|
||||
for (0.., haystack) |i, key| {
|
||||
if (key.eql(needle)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const Sides = struct {
|
||||
top: f32 = 0,
|
||||
bottom: f32 = 0,
|
||||
left: f32 = 0,
|
||||
right: f32 = 0,
|
||||
|
||||
pub fn vertical(self: *Sides, amount: f32) void {
|
||||
self.top = amount;
|
||||
self.bottom = amount;
|
||||
}
|
||||
|
||||
pub fn horizontal(self: *Sides, amount: f32) void {
|
||||
self.left = amount;
|
||||
self.right = amount;
|
||||
}
|
||||
|
||||
pub fn all(self: *Sides, amount: f32) void {
|
||||
self.top = amount;
|
||||
self.bottom = amount;
|
||||
self.left = amount;
|
||||
self.right = amount;
|
||||
}
|
||||
};
|
||||
|
||||
key: Key = .{},
|
||||
parent: Key = .{},
|
||||
flags: Flags = .{},
|
||||
layout: Layout = .{},
|
||||
|
||||
padding: Sides = .{},
|
||||
corner_radius: f32 = 12,
|
||||
|
||||
size: SemanticVec2 = .{
|
||||
.x = .{ .pixels = 0 },
|
||||
.y = .{ .pixels = 0 },
|
||||
},
|
||||
|
||||
text: ?struct {
|
||||
content: []const u8,
|
||||
color: rl.Color = srcery.bright_white,
|
||||
} = null,
|
||||
|
||||
background: ?union(enum) {
|
||||
color: rl.Color,
|
||||
blur_world,
|
||||
} = null,
|
||||
|
||||
computed_relative_position: ?Vec2 = null,
|
||||
computed_content_size: ?Vec2 = null,
|
||||
computed_rect: ?Rect = null,
|
||||
|
||||
fn computed_size(self: *const Widget) ?Vec2 {
|
||||
if (self.computed_content_size) |content_size| {
|
||||
return Vec2{
|
||||
.x = content_size.x + self.padding.left + self.padding.right,
|
||||
.y = content_size.y + self.padding.top + self.padding.bottom,
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Layout = struct {
|
||||
pub const Kind = enum {
|
||||
vertical_column
|
||||
};
|
||||
|
||||
kind: Kind = .vertical_column,
|
||||
gap: f32 = 0,
|
||||
key_extra: u16 = 0
|
||||
};
|
||||
|
||||
const debug = false;
|
||||
|
||||
const max_widgets = 64;
|
||||
const Widgets = std.BoundedArray(Widget, max_widgets);
|
||||
|
||||
const random_seed = 0;
|
||||
const root_widget_key = Widget.Key{ .hash = 0 };
|
||||
|
||||
prev_widgets: Widgets = .{},
|
||||
widgets: Widgets = .{},
|
||||
widget_stack: std.BoundedArray(Widget.Key, max_widgets) = .{},
|
||||
font_face: FontFace,
|
||||
random: std.Random.DefaultPrng,
|
||||
blur_background: ?rl.Texture = null,
|
||||
disable_mouse_interaction: bool = false,
|
||||
|
||||
// Debug fields
|
||||
duplicate_keys: std.BoundedArray(Widget.Key, max_widgets) = .{},
|
||||
|
||||
pub fn init(font_face: FontFace) UI {
|
||||
const random = std.Random.DefaultPrng.init(random_seed);
|
||||
return UI{
|
||||
.transform_stack = stack
|
||||
.font_face = font_face,
|
||||
.random = random
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isHot(self: *const UI, id: Id) bool {
|
||||
if (self.hot_widget) |hot_id| {
|
||||
return hot_id.eql(id);
|
||||
pub fn begin(self: *UI) void {
|
||||
self.prev_widgets = self.widgets;
|
||||
self.widgets.len = 0;
|
||||
self.duplicate_keys.len = 0;
|
||||
self.random = std.Random.DefaultPrng.init(random_seed);
|
||||
|
||||
self.widgets.appendAssumeCapacity(Widget{
|
||||
.key = root_widget_key,
|
||||
.size = .{
|
||||
.x = .{ .pixels = @floatFromInt(rl.getScreenWidth()) },
|
||||
.y = .{ .pixels = @floatFromInt(rl.getScreenHeight()) },
|
||||
},
|
||||
.flags = Widget.Flags.initMany(&.{ .passthrough })
|
||||
});
|
||||
self.pushWidget(root_widget_key);
|
||||
}
|
||||
|
||||
pub fn end(self: *UI) void {
|
||||
self.popWidget();
|
||||
|
||||
const widgets: []Widget = self.widgets.slice();
|
||||
|
||||
for (widgets) |*widget| {
|
||||
widget.computed_content_size = Vec2{ .x = 0, .y = 0 };
|
||||
widget.computed_relative_position = Vec2{ .x = 0, .y = 0 };
|
||||
}
|
||||
|
||||
// (1) Calculate stadalone sizes
|
||||
for (widgets) |*widget| {
|
||||
var text_size = Vec2{ .x = 0, .y = 0 };
|
||||
if (widget.text) |text| {
|
||||
text_size = self.font_face.measureText(text.content);
|
||||
}
|
||||
|
||||
var content_size = &widget.computed_content_size.?;
|
||||
inline for (.{ "x", "y" }) |axis| {
|
||||
const semantic_size = @field(widget.size, axis);
|
||||
|
||||
if (semantic_size == .pixels) {
|
||||
@field(content_size, axis) = semantic_size.pixels;
|
||||
} else if (semantic_size == .text) {
|
||||
@field(content_size, axis) = @field(text_size, axis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (2) Upward dependent sizes
|
||||
for (widgets) |*widget| {
|
||||
if (widget.key.eql(root_widget_key)) continue;
|
||||
|
||||
const parent = self.getWidget(widget.parent);
|
||||
const parent_size = parent.computed_size().?;
|
||||
|
||||
var content_size = &widget.computed_content_size.?;
|
||||
|
||||
if (widget.size.x == .percent) {
|
||||
const percent = widget.size.x.percent;
|
||||
content_size.x = parent_size.x * percent;
|
||||
}
|
||||
|
||||
if (widget.size.y == .percent) {
|
||||
const percent = widget.size.y.percent;
|
||||
content_size.y = parent_size.y * percent;
|
||||
}
|
||||
}
|
||||
|
||||
for (0..widgets.len) |i| {
|
||||
const widget = &widgets[widgets.len - i - 1];
|
||||
var children = self.listChildren(widget);
|
||||
|
||||
// (3) Calculate relative position of each widget
|
||||
switch (widget.layout.kind) {
|
||||
.vertical_column => {
|
||||
var y_offset: f32 = 0;
|
||||
const children_slice: []*Widget = children.slice();
|
||||
for (children_slice) |child| {
|
||||
child.computed_relative_position.?.y += y_offset;
|
||||
y_offset += child.computed_size().?.y;
|
||||
y_offset += widget.layout.gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (4) Downward dependent sizes
|
||||
inline for (.{ "x", "y" }) |axis| {
|
||||
if (@field(widget.size, axis) == .fit_children) {
|
||||
var min = std.math.floatMax(f32);
|
||||
var max = std.math.floatMin(f32);
|
||||
|
||||
for (children.constSlice()) |child| {
|
||||
const child_size = child.computed_size().?;
|
||||
const child_position = child.computed_relative_position.?;
|
||||
|
||||
min = @min(min, @field(child_position, axis));
|
||||
max = @max(max, @field(child_position, axis) + @field(child_size, axis));
|
||||
}
|
||||
|
||||
if (children.len > 0) {
|
||||
@field(widget.computed_content_size.?, axis) = max - min;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (widgets) |*widget| {
|
||||
const computed_size = widget.computed_size().?;
|
||||
const relative_position = widget.computed_relative_position.?;
|
||||
|
||||
var computed_rect = Rect{
|
||||
.x = relative_position.x,
|
||||
.y = relative_position.y,
|
||||
.width = computed_size.x,
|
||||
.height = computed_size.y,
|
||||
};
|
||||
|
||||
if (!widget.key.eql(root_widget_key)) {
|
||||
const parent = self.getWidget(widget.parent);
|
||||
computed_rect.x += parent.padding.left;
|
||||
computed_rect.y += parent.padding.top;
|
||||
|
||||
if (parent.computed_rect) |parent_rect| {
|
||||
computed_rect.x += parent_rect.x;
|
||||
computed_rect.y += parent_rect.y;
|
||||
}
|
||||
}
|
||||
|
||||
widget.computed_rect = computed_rect;
|
||||
}
|
||||
|
||||
const duplicate_keys = self.duplicate_keys.constSlice();
|
||||
for (widgets) |*widget|{
|
||||
const rect = widget.computed_rect orelse continue;
|
||||
|
||||
const padding = widget.padding;
|
||||
const content_rect = Rect{
|
||||
.x = rect.x + padding.left,
|
||||
.y = rect.y + padding.top,
|
||||
.width = rect.width - padding.left - padding.right,
|
||||
.height = rect.height - padding.top - padding.bottom,
|
||||
};
|
||||
|
||||
if (widget.background) |background| {
|
||||
switch (background) {
|
||||
.color => |bg_color| {
|
||||
drawRectangleRoundedUV(rect, widget.corner_radius, bg_color);
|
||||
},
|
||||
.blur_world => {
|
||||
const bg_color = srcery.xgray10;
|
||||
|
||||
if (self.blur_background) |texture| {
|
||||
const border = 2.5;
|
||||
|
||||
{
|
||||
const previous_texture = rl.getShapesTexture();
|
||||
const previous_rect = rl.getShapesTextureRectangle();
|
||||
defer rl.setShapesTexture(previous_texture, previous_rect);
|
||||
|
||||
const texture_height: f32 = @floatFromInt(texture.height);
|
||||
const shape_rect = rl.Rectangle{
|
||||
.x = rect.x,
|
||||
.y = texture_height - rect.y,
|
||||
.width = rect.width,
|
||||
.height = -rect.height,
|
||||
};
|
||||
rl.setShapesTexture(texture, shape_rect);
|
||||
|
||||
drawRectangleRoundedUV(rect, widget.corner_radius, bg_color);
|
||||
rl.gl.rlDrawRenderBatchActive();
|
||||
}
|
||||
|
||||
drawRectangleRoundedLinesEx(
|
||||
rect,
|
||||
widget.corner_radius,
|
||||
border,
|
||||
srcery.bright_white.alpha(0.25)
|
||||
);
|
||||
rl.gl.rlDrawRenderBatchActive();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (widget.text) |text| {
|
||||
self.font_face.drawTextCenter(
|
||||
text.content,
|
||||
rect_utils.center(content_rect),
|
||||
text.color
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (builtin.mode == .Debug) {
|
||||
for (widgets) |widget|{
|
||||
const rect = widget.computed_rect orelse continue;
|
||||
|
||||
if (Widget.Key.indexOf(duplicate_keys, widget.key) != null) {
|
||||
const time: f32 = @floatCast(rl.getTime());
|
||||
if (@rem(time, 0.4) < 0.2) {
|
||||
rl.drawRectangleLinesEx(rect, 3, rl.Color.purple);
|
||||
} else {
|
||||
rl.drawRectangleLinesEx(rect, 3, rl.Color.red);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pushWidget(self: *UI, key: Widget.Key) void {
|
||||
self.widget_stack.appendAssumeCapacity(key);
|
||||
}
|
||||
|
||||
pub fn popWidget(self: *UI) void {
|
||||
assert(self.widget_stack.len >= 1);
|
||||
|
||||
_ = self.widget_stack.pop();
|
||||
}
|
||||
|
||||
pub fn topWidget(self: *UI) *Widget {
|
||||
const top_key = self.widget_stack.buffer[self.widget_stack.len - 1];
|
||||
return self.getWidget(top_key);
|
||||
}
|
||||
|
||||
pub fn layout(self: *UI) *Layout {
|
||||
return &self.topWidget().layout;
|
||||
}
|
||||
|
||||
pub fn getOrAppendWidget(self: *UI, key_hash: u16) *Widget {
|
||||
const parent = self.topWidget();
|
||||
|
||||
const key = Widget.Key{
|
||||
.hash = key_hash,
|
||||
.extra = parent.layout.key_extra
|
||||
};
|
||||
|
||||
var found_prev_widget: ?Widget = null;
|
||||
for (self.prev_widgets.constSlice()) |widget| {
|
||||
if (widget.key.eql(key)) {
|
||||
found_prev_widget = widget;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (self.widgets.constSlice()) |widget| {
|
||||
if (widget.key.eql(key)) {
|
||||
self.duplicate_keys.appendAssumeCapacity(widget.key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_prev_widget) |prev_widget| {
|
||||
self.widgets.appendAssumeCapacity(prev_widget);
|
||||
} else {
|
||||
self.widgets.appendAssumeCapacity(Widget{
|
||||
.key = key
|
||||
});
|
||||
}
|
||||
|
||||
const widget = &self.widgets.buffer[self.widgets.len - 1];
|
||||
|
||||
assert(self.widget_stack.len >= 1);
|
||||
widget.parent = self.widget_stack.buffer[self.widget_stack.len-1];
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
pub fn getWidget(self: *UI, key: Widget.Key) *Widget {
|
||||
for (self.widgets.slice()) |*widget| {
|
||||
if (widget.key.eql(key)) {
|
||||
return widget;
|
||||
}
|
||||
}
|
||||
|
||||
@panic("Failed to find widget");
|
||||
}
|
||||
|
||||
pub fn getInteraction(self: *UI, widget: *const Widget) Interaction {
|
||||
var interaction = Interaction{
|
||||
.widget = widget.key
|
||||
};
|
||||
|
||||
const rect = widget.computed_rect orelse return interaction;
|
||||
|
||||
if (!self.disable_mouse_interaction) {
|
||||
const mouse = rl.getMousePosition();
|
||||
if (!widget.flags.contains(.passthrough) and rect_utils.isInsideVec2(rect, mouse)) {
|
||||
interaction.hovering = true;
|
||||
|
||||
if (rl.isMouseButtonPressed(.mouse_button_left)) {
|
||||
interaction.pressed = true;
|
||||
}
|
||||
|
||||
if (rl.isMouseButtonReleased(.mouse_button_left)) {
|
||||
interaction.released = true;
|
||||
if (widget.flags.contains(.clickable)) {
|
||||
interaction.clicked = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (rl.isMouseButtonDown(.mouse_button_left)) {
|
||||
interaction.held_down = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return interaction;
|
||||
}
|
||||
|
||||
pub fn isHoveringAnything(self: *UI) bool {
|
||||
const widgets: []Widget = self.widgets.slice();
|
||||
for (widgets) |*widget|{
|
||||
const interaction = self.getInteraction(widget);
|
||||
if (interaction.hovering) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn isActive(self: *const UI, id: Id) bool {
|
||||
if (self.active_widget) |active_id| {
|
||||
return active_id.eql(id);
|
||||
}
|
||||
return false;
|
||||
pub fn randomWidgetHash(self: *UI) u16 {
|
||||
const rng = self.random.random();
|
||||
return rng.int(u16);
|
||||
}
|
||||
|
||||
pub fn hashSrc(src: SourceLocation) u64 {
|
||||
var hash = std.hash.Fnv1a_64.init();
|
||||
hash.update(src.file);
|
||||
hash.update(std.mem.asBytes(&src.line));
|
||||
hash.update(std.mem.asBytes(&src.column));
|
||||
return hash.value;
|
||||
fn listChildren(self: *UI, parent: *Widget) std.BoundedArray(*Widget, max_widgets) {
|
||||
var children: std.BoundedArray(*Widget, max_widgets) = .{};
|
||||
|
||||
const widgets: []Widget = self.widgets.slice();
|
||||
for (widgets) |*child| {
|
||||
if (child == parent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fn getTopFrame(self: *UI) *TransformFrame {
|
||||
assert(self.transform_stack.len >= 1);
|
||||
return &self.transform_stack.buffer[self.transform_stack.len-1];
|
||||
if (child.parent.eql(parent.key)) {
|
||||
children.appendAssumeCapacity(child);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getMousePosition(self: *UI) rl.Vector2 {
|
||||
const frame = self.getTopFrame();
|
||||
return rl.getMousePosition().subtract(frame.offset).divide(frame.scale);
|
||||
return children;
|
||||
}
|
||||
|
||||
pub fn getMouseDelta(self: *UI) rl.Vector2 {
|
||||
const frame = self.getTopFrame();
|
||||
return rl.Vector2.multiply(rl.getMouseDelta(), frame.scale);
|
||||
// Modified version of `DrawRectangleRounded` where the UV texture coordiantes are consistent and align
|
||||
fn drawRectangleRoundedUV(rec: rl.Rectangle, radius: f32, color: rl.Color) void {
|
||||
if (radius <= 0 or rec.width <= 1 or rec.height <= 1) {
|
||||
rl.drawRectangleRec(rec, color);
|
||||
return;
|
||||
}
|
||||
|
||||
pub fn getMouseWheelMove(self: *UI) f32 {
|
||||
const frame = self.getTopFrame();
|
||||
return rl.getMouseWheelMove() * frame.scale.y;
|
||||
if (radius <= 0.0) return;
|
||||
|
||||
// Calculate the maximum angle between segments based on the error rate (usually 0.5f)
|
||||
const smooth_circle_error_rate = 0.5;
|
||||
const th: f32 = std.math.acos(2 * std.math.pow(f32, 1 - smooth_circle_error_rate / radius, 2) - 1);
|
||||
var segments: i32 = @intFromFloat(@ceil(2 * std.math.pi / th) / 4.0);
|
||||
segments = @max(segments, 4);
|
||||
|
||||
const step_length = 90.0 / @as(f32, @floatFromInt(segments));
|
||||
|
||||
// Quick sketch to make sense of all of this,
|
||||
// there are 9 parts to draw, also mark the 12 points we'll use
|
||||
//
|
||||
// P0____________________P1
|
||||
// /| |\
|
||||
// /1| 2 |3\
|
||||
// P7 /__|____________________|__\ P2
|
||||
// | |P8 P9| |
|
||||
// | 8 | 9 | 4 |
|
||||
// | __|____________________|__ |
|
||||
// P6 \ |P11 P10| / P3
|
||||
// \7| 6 |5/
|
||||
// \|____________________|/
|
||||
// P5 P4
|
||||
|
||||
// Coordinates of the 12 points that define the rounded rect
|
||||
const radius_u = radius / rec.width;
|
||||
const radius_v = radius / rec.height;
|
||||
const points = [_]rl.Vector2{
|
||||
.{ .x = radius_u , .y = 0 }, // P0
|
||||
.{ .x = 1 - radius_u , .y = 0 }, // P1
|
||||
.{ .x = 1 , .y = radius_v }, // P2
|
||||
.{ .x = 1 , .y = 1 - radius_v }, // P3
|
||||
.{ .x = 1 - radius_u , .y = 1 }, // P4
|
||||
.{ .x = radius_u , .y = 1 }, // P5
|
||||
.{ .x = 0 , .y = 1 - radius_v }, // P6
|
||||
.{ .x = 0 , .y = radius_v }, // P7
|
||||
.{ .x = radius_u , .y = radius_v }, // P8
|
||||
.{ .x = 1 - radius_u , .y = radius_v }, // P9
|
||||
.{ .x = 1 - radius_u , .y = 1 - radius_v }, // P10
|
||||
.{ .x = radius_u , .y = 1 - radius_v }, // P11
|
||||
};
|
||||
|
||||
const texture = rl.getShapesTexture();
|
||||
const shape_rect = rl.getShapesTextureRectangle();
|
||||
|
||||
const texture_width: f32 = @floatFromInt(texture.width);
|
||||
const texture_height: f32 = @floatFromInt(texture.height);
|
||||
|
||||
rl.gl.rlBegin(rlgl_h.RL_TRIANGLES);
|
||||
defer rl.gl.rlEnd();
|
||||
|
||||
rl.gl.rlSetTexture(texture.id);
|
||||
defer rl.gl.rlSetTexture(0);
|
||||
|
||||
// Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner
|
||||
const centers = [_]rl.Vector2{ points[8], points[9], points[10], points[11] };
|
||||
const angles = [_]f32{ 180.0, 270.0, 0.0, 90.0 };
|
||||
for (0..4) |k| {
|
||||
var angle = angles[k];
|
||||
const center = centers[k];
|
||||
for (0..@intCast(segments)) |_| {
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
|
||||
const rad_per_deg = std.math.rad_per_deg;
|
||||
|
||||
const triangle = .{
|
||||
center,
|
||||
.{
|
||||
.x = center.x + @cos(rad_per_deg*(angle + step_length))*radius_u,
|
||||
.y = center.y + @sin(rad_per_deg*(angle + step_length))*radius_v
|
||||
},
|
||||
.{
|
||||
.x = center.x + @cos(rad_per_deg * angle)*radius_u,
|
||||
.y = center.y + @sin(rad_per_deg * angle)*radius_v
|
||||
}
|
||||
};
|
||||
|
||||
pub fn isMouseInside(self: *UI, rect: rl.Rectangle) bool {
|
||||
return rect_utils.isInsideVec2(rect, self.getMousePosition());
|
||||
}
|
||||
|
||||
pub fn transformScale(self: *UI, x: f32, y: f32) void {
|
||||
const frame = self.getTopFrame();
|
||||
frame.scale.x *= x;
|
||||
frame.scale.y *= y;
|
||||
|
||||
rl.gl.rlScalef(x, y, 1);
|
||||
}
|
||||
|
||||
pub fn transformTranslate(self: *UI, x: f32, y: f32) void {
|
||||
const frame = self.getTopFrame();
|
||||
frame.offset.x += x * frame.scale.x;
|
||||
frame.offset.y += y * frame.scale.y;
|
||||
|
||||
rl.gl.rlTranslatef(x, y, 0);
|
||||
}
|
||||
|
||||
pub fn pushTransform(self: *UI) void {
|
||||
rl.gl.rlPushMatrix();
|
||||
self.transform_stack.appendAssumeCapacity(self.getTopFrame().*);
|
||||
}
|
||||
|
||||
pub fn popTransform(self: *UI) void {
|
||||
assert(self.transform_stack.len >= 2);
|
||||
rl.gl.rlPopMatrix();
|
||||
_ = self.transform_stack.pop();
|
||||
}
|
||||
|
||||
pub fn beginScissorMode(self: *UI, x: f32, y: f32, width: f32, height: f32) void {
|
||||
const frame = self.getTopFrame();
|
||||
|
||||
rl.beginScissorMode(
|
||||
@intFromFloat(x * frame.scale.x + frame.offset.x),
|
||||
@intFromFloat(y * frame.scale.y + frame.offset.y),
|
||||
@intFromFloat(width * frame.scale.x),
|
||||
@intFromFloat(height * frame.scale.y),
|
||||
inline for (triangle) |point| {
|
||||
rl.gl.rlTexCoord2f(
|
||||
(shape_rect.x + shape_rect.width * point.x) / texture_width,
|
||||
(shape_rect.y + shape_rect.height * point.y) / texture_height
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + rec.width * point.x,
|
||||
rec.y + rec.height * point.y
|
||||
);
|
||||
}
|
||||
|
||||
pub fn beginScissorModeRect(self: *UI, rect: rl.Rectangle) void {
|
||||
self.beginScissorMode(rect.x, rect.y, rect.width, rect.height);
|
||||
angle += step_length;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn endScissorMode(self: *UI) void {
|
||||
_ = self;
|
||||
rl.endScissorMode();
|
||||
// [2] Upper Rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 0, 8, 9, 1, 0, 9 }) |index| {
|
||||
const point = points[index];
|
||||
rl.gl.rlTexCoord2f(
|
||||
(shape_rect.x + shape_rect.width * point.x) / texture_width,
|
||||
(shape_rect.y + shape_rect.height * point.y) / texture_height
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + rec.width * point.x,
|
||||
rec.y + rec.height * point.y
|
||||
);
|
||||
}
|
||||
|
||||
pub const Id = struct {
|
||||
location: u64,
|
||||
extra: u32 = 0,
|
||||
|
||||
pub fn init(comptime src: SourceLocation) Id {
|
||||
return Id{ .location = comptime hashSrc(src) };
|
||||
// [4] Right Rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 9, 10, 3, 2, 9, 3 }) |index| {
|
||||
const point = points[index];
|
||||
rl.gl.rlTexCoord2f(
|
||||
(shape_rect.x + shape_rect.width * point.x) / texture_width,
|
||||
(shape_rect.y + shape_rect.height * point.y) / texture_height
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + rec.width * point.x,
|
||||
rec.y + rec.height * point.y
|
||||
);
|
||||
}
|
||||
|
||||
pub fn eql(a: Id, b: Id) bool {
|
||||
return a.location == b.location and a.extra == b.extra;
|
||||
// [6] Bottom Rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 11, 5, 4, 10, 11, 4 }) |index| {
|
||||
const point = points[index];
|
||||
rl.gl.rlTexCoord2f(
|
||||
(shape_rect.x + shape_rect.width * point.x) / texture_width,
|
||||
(shape_rect.y + shape_rect.height * point.y) / texture_height
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + rec.width * point.x,
|
||||
rec.y + rec.height * point.y
|
||||
);
|
||||
}
|
||||
|
||||
// [8] Left Rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 7, 6, 11, 8, 7, 11 }) |index| {
|
||||
const point = points[index];
|
||||
rl.gl.rlTexCoord2f(
|
||||
(shape_rect.x + shape_rect.width * point.x) / texture_width,
|
||||
(shape_rect.y + shape_rect.height * point.y) / texture_height
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + rec.width * point.x,
|
||||
rec.y + rec.height * point.y
|
||||
);
|
||||
}
|
||||
|
||||
// [9] Middle Rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 8, 11, 10, 9, 8, 10 }) |index| {
|
||||
const point = points[index];
|
||||
rl.gl.rlTexCoord2f(
|
||||
(shape_rect.x + shape_rect.width * point.x) / texture_width,
|
||||
(shape_rect.y + shape_rect.height * point.y) / texture_height
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + rec.width * point.x,
|
||||
rec.y + rec.height * point.y
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Modified version of `DrawRectangleRoundedLinesEx` where the corner radius is provided
|
||||
fn drawRectangleRoundedLinesEx(rec: rl.Rectangle, radius: f32, _line_thick: f32, color: rl.Color) void {
|
||||
var line_thick = _line_thick;
|
||||
if (line_thick < 0) line_thick = 0;
|
||||
|
||||
// Not a rounded rectangle
|
||||
if (radius <= 0.0) {
|
||||
rl.drawRectangleLinesEx(rl.Rectangle{
|
||||
.x = rec.x,
|
||||
.y = rec.y,
|
||||
.width = rec.width,
|
||||
.height = rec.height
|
||||
}, line_thick, color);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate number of segments to use for the corners
|
||||
const smooth_circle_error_rate = 0.5;
|
||||
const th: f32 = std.math.acos(2 * std.math.pow(f32, 1 - smooth_circle_error_rate / radius, 2) - 1);
|
||||
var segments: i32 = @intFromFloat(@ceil(2 * std.math.pi / th) / 4.0);
|
||||
segments = @max(segments, 4);
|
||||
|
||||
const step_length = 90.0/@as(f32, @floatFromInt(segments));
|
||||
const outer_radius = radius;
|
||||
const inner_radius = radius - line_thick;
|
||||
|
||||
// Quick sketch to make sense of all of this,
|
||||
// marks the 16 + 4(corner centers P16-19) points we'll use
|
||||
//
|
||||
// P0 ================== P1
|
||||
// // P8 P9 \\
|
||||
// // \\
|
||||
// P7 // P15 P10 \\ P2
|
||||
// || *P16 P17* ||
|
||||
// || ||
|
||||
// || P14 P11 ||
|
||||
// P6 \\ *P19 P18* // P3
|
||||
// \\ //
|
||||
// \\ P13 P12 //
|
||||
// P5 ================== P4
|
||||
const points = [_]rl.Vector2{
|
||||
.{ .x = outer_radius , .y = 0 }, // P0
|
||||
.{ .x = rec.width - outer_radius , .y = 0 }, // P1
|
||||
.{ .x = rec.width , .y = outer_radius }, // P2
|
||||
.{ .x = rec.width , .y = rec.height - outer_radius }, // P3
|
||||
.{ .x = rec.width - outer_radius , .y = rec.height }, // P4
|
||||
.{ .x = outer_radius , .y = rec.height }, // P5
|
||||
.{ .x = 0 , .y = rec.height - outer_radius }, // P6
|
||||
.{ .x = 0 , .y = outer_radius }, // P7
|
||||
|
||||
.{ .x = outer_radius , .y = line_thick }, // P8
|
||||
.{ .x = rec.width - outer_radius , .y = line_thick }, // P9
|
||||
.{ .x = rec.width - line_thick , .y = outer_radius }, // P10
|
||||
.{ .x = rec.width - line_thick , .y = rec.height - outer_radius }, // P11
|
||||
.{ .x = rec.width - outer_radius , .y = rec.height - line_thick }, // P12
|
||||
.{ .x = outer_radius , .y = rec.height - line_thick }, // P13
|
||||
.{ .x = line_thick , .y = rec.height - outer_radius }, // P14
|
||||
.{ .x = line_thick , .y = outer_radius }, // P15
|
||||
};
|
||||
|
||||
pub const Stack = struct {
|
||||
pub const Direction = enum {
|
||||
top_to_bottom,
|
||||
bottom_to_top,
|
||||
left_to_right
|
||||
const centers = [_]rl.Vector2{
|
||||
.{ .x = outer_radius , .y = outer_radius }, // P16
|
||||
.{ .x = rec.width - outer_radius , .y = outer_radius }, // P17
|
||||
.{ .x = rec.width - outer_radius , .y = rec.height - outer_radius }, // P18
|
||||
.{ .x = outer_radius , .y = rec.height - outer_radius }, // P18
|
||||
};
|
||||
|
||||
unused_box: rl.Rectangle,
|
||||
dir: Direction,
|
||||
gap: f32 = 0,
|
||||
const angles = [_]f32{ 180.0, 270.0, 0.0, 90.0 };
|
||||
|
||||
pub fn init(box: rl.Rectangle, dir: Direction) Stack {
|
||||
return Stack{
|
||||
.unused_box = box,
|
||||
.dir = dir
|
||||
};
|
||||
}
|
||||
if (line_thick > 1) {
|
||||
rl.gl.rlBegin(rlgl_h.RL_TRIANGLES);
|
||||
defer rl.gl.rlEnd();
|
||||
|
||||
pub fn next(self: *Stack, size: f32) rl.Rectangle {
|
||||
return switch (self.dir) {
|
||||
.top_to_bottom => {
|
||||
const next_box = rl.Rectangle.init(self.unused_box.x, self.unused_box.y, self.unused_box.width, size);
|
||||
self.unused_box.y += size;
|
||||
self.unused_box.y += self.gap;
|
||||
return next_box;
|
||||
// Draw all of the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner
|
||||
for (centers, angles) |center, initialAngle| {
|
||||
var angle = initialAngle;
|
||||
for (0..@intCast(segments)) |_| {
|
||||
|
||||
const rad_per_deg = std.math.rad_per_deg;
|
||||
const next_angle = angle + step_length;
|
||||
|
||||
const rect_points = .{
|
||||
rl.Vector2{
|
||||
.x = @cos(rad_per_deg * angle) * inner_radius,
|
||||
.y = @sin(rad_per_deg * angle) * inner_radius
|
||||
},
|
||||
.bottom_to_top => {
|
||||
const next_box = rl.Rectangle.init(self.unused_box.x, self.unused_box.y + self.unused_box.height - size, self.unused_box.width, size);
|
||||
self.unused_box.height -= size;
|
||||
self.unused_box.height -= self.gap;
|
||||
return next_box;
|
||||
rl.Vector2{
|
||||
.x = @cos(rad_per_deg * next_angle) * inner_radius,
|
||||
.y = @sin(rad_per_deg * next_angle) * inner_radius
|
||||
},
|
||||
.left_to_right => {
|
||||
const next_box = rl.Rectangle.init(self.unused_box.x, self.unused_box.y, size, self.unused_box.height);
|
||||
self.unused_box.x += size;
|
||||
self.unused_box.x += self.gap;
|
||||
return next_box;
|
||||
rl.Vector2{
|
||||
.x = @cos(rad_per_deg * angle) * outer_radius,
|
||||
.y = @sin(rad_per_deg * angle) * outer_radius
|
||||
},
|
||||
};
|
||||
rl.Vector2{
|
||||
.x = @cos(rad_per_deg * next_angle) * outer_radius,
|
||||
.y = @sin(rad_per_deg * next_angle) * outer_radius
|
||||
}
|
||||
};
|
||||
|
||||
pub const IdIterator = struct {
|
||||
id: Id,
|
||||
counter: u32,
|
||||
|
||||
pub fn init(comptime src: SourceLocation) IdIterator {
|
||||
return IdIterator{
|
||||
.id = Id.init(src),
|
||||
.counter = 0
|
||||
};
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 0, 1, 2, 1, 3, 2 }) |i| {
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + center.x + rect_points[i].x,
|
||||
rec.y + center.y + rect_points[i].y
|
||||
);
|
||||
}
|
||||
|
||||
pub fn next(self: *IdIterator) Id {
|
||||
var id = self.id;
|
||||
id.extra = self.counter;
|
||||
|
||||
self.counter += 1;
|
||||
return id;
|
||||
angle = next_angle;
|
||||
}
|
||||
}
|
||||
|
||||
// Upper rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 0, 8, 9, 1, 0, 9 }) |i| {
|
||||
rl.gl.rlVertex2f(rec.x + points[i].x, rec.y + points[i].y);
|
||||
}
|
||||
|
||||
// Right rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 10, 11, 3, 2, 10, 3 }) |i| {
|
||||
rl.gl.rlVertex2f(rec.x + points[i].x, rec.y + points[i].y);
|
||||
}
|
||||
|
||||
// Lower rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 13, 5, 4, 12, 13, 4 }) |i| {
|
||||
rl.gl.rlVertex2f(rec.x + points[i].x, rec.y + points[i].y);
|
||||
}
|
||||
|
||||
// Left rectangle
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
inline for (.{ 7, 6, 14, 15, 7, 14 }) |i| {
|
||||
rl.gl.rlVertex2f(rec.x + points[i].x, rec.y + points[i].y);
|
||||
}
|
||||
} else {
|
||||
// Use LINES to draw the outline
|
||||
rl.gl.rlBegin(rlgl_h.RL_LINES);
|
||||
defer rl.gl.rlEnd();
|
||||
|
||||
// Draw all the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner
|
||||
for (centers, angles) |center, initialAngle| {
|
||||
var angle = initialAngle;
|
||||
|
||||
for (0..@intCast(segments)) |_| {
|
||||
const rad_per_deg = std.math.rad_per_deg;
|
||||
const next_angle = angle + step_length;
|
||||
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + center.x + @cos(rad_per_deg*angle)*outer_radius,
|
||||
rec.y + center.y + @sin(rad_per_deg*angle)*outer_radius
|
||||
);
|
||||
rl.gl.rlVertex2f(
|
||||
rec.x + center.x + @cos(rad_per_deg*next_angle)*outer_radius,
|
||||
rec.y + center.y + @sin(rad_per_deg*next_angle)*outer_radius
|
||||
);
|
||||
|
||||
angle = next_angle;
|
||||
}
|
||||
}
|
||||
|
||||
// And now the remaining 4 lines
|
||||
inline for (.{ 0, 2, 4, 6 }) |i| {
|
||||
rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
rl.gl.rlVertex2f(rec.x + points[i + 0].x, rec.y + points[i + 0].y);
|
||||
rl.gl.rlVertex2f(rec.x + points[i + 1].x, rec.y + points[i + 1].y);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -275,7 +275,7 @@ pub fn ArtificerType(Clock: type, Server: type) type {
|
||||
return slot;
|
||||
}
|
||||
|
||||
fn timeUntilCooldownExpires(self: *Self) u64 {
|
||||
pub fn timeUntilCooldownExpires(self: *Self) u64 {
|
||||
const store = self.server.store;
|
||||
|
||||
const character = store.characters.get(self.character).?;
|
||||
|
@ -3,9 +3,25 @@ const std = @import("std");
|
||||
const Clock = @This();
|
||||
|
||||
timestamp: i128 = 0,
|
||||
timestamp_limit: ?i128 = null,
|
||||
|
||||
pub fn sleep(self: *Clock, nanoseconds: u64) void {
|
||||
self.timestamp += @intCast(nanoseconds);
|
||||
const nanoseconds_i128: i128 = @intCast(nanoseconds);
|
||||
const new_timestamp = self.timestamp + nanoseconds_i128;
|
||||
|
||||
if (self.timestamp_limit != null) {
|
||||
while (true) {
|
||||
self.timestamp = @min(self.timestamp_limit.?, new_timestamp);
|
||||
|
||||
if (self.timestamp < self.timestamp_limit.?) {
|
||||
break;
|
||||
}
|
||||
|
||||
std.time.sleep(std.time.ms_per_s * 100);
|
||||
}
|
||||
} else {
|
||||
self.timestamp = new_timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nanoTimestamp(self: Clock) i128 {
|
||||
|
@ -6,7 +6,7 @@ pub fn sleep(self: *Clock, nanoseconds: u64) void {
|
||||
std.time.sleep(nanoseconds);
|
||||
}
|
||||
|
||||
pub fn nanoTimestamp(self: *Clock) i128 {
|
||||
pub fn nanoTimestamp(self: Clock) i128 {
|
||||
_ = self;
|
||||
return std.time.nanoTimestamp();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user