Skip to content

Commit 9b59bbc

Browse files
committed
Merge branch 'main' of https://github.com/HTRMC/FarHorizons
2 parents 506c837 + ba40a7a commit 9b59bbc

8 files changed

Lines changed: 170 additions & 8 deletions

File tree

.vscode/tasks.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@
1919
"args": ["build", "run"],
2020
"group": "build",
2121
"problemMatcher": []
22+
},
23+
{
24+
"label": "Build and Run (Tracy)",
25+
"type": "shell",
26+
"command": "./compiler/zig/zig.exe",
27+
"args": ["build", "run", "-Dtracy=true"],
28+
"group": "build",
29+
"problemMatcher": []
2230
}
2331
]
2432
}

.zigversion

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.16.0-dev.1859+212968c57
1+
0.16.0-dev.2368+380ea6fb5

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 HTRMC
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,4 @@ A voxel-based game/game-engine written in Zig with a Vulkan renderer. Inspired b
7272

7373
## License
7474

75-
This project is licensed under All Rights Reserved.
75+
This project is licensed under MIT.

src/client/Main.zig

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const DisplayData = shared.DisplayData;
99
const GameData = shared.GameData;
1010
const QuickPlayData = shared.QuickPlayData;
1111
const CrashReport = shared.CrashReport;
12+
const profiler = shared.profiler;
1213
const FarHorizonsClient = @import("FarHorizonsClient.zig").FarHorizonsClient;
1314

1415
pub const Main = struct {
@@ -22,7 +23,10 @@ pub const Main = struct {
2223
pub fn run(io: Io) !void {
2324
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
2425
defer _ = gpa.deinit();
25-
const allocator = gpa.allocator();
26+
27+
// Wrap allocator with Tracy for memory profiling (compiles to no-op when tracy disabled)
28+
var tracy_alloc = profiler.TracyAllocator(null).init(gpa.allocator());
29+
const allocator = tracy_alloc.allocator();
2630

2731
// Use arena for strings that live for the program's lifetime
2832
var arena = std.heap.ArenaAllocator.init(allocator);
@@ -73,7 +77,9 @@ pub const Main = struct {
7377
};
7478

7579
fn parseArgs(allocator: std.mem.Allocator) !ParsedArgs {
76-
var args = try std.process.argsWithAllocator(allocator);
80+
const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
81+
const cmd_line_slice = cmd_line.Buffer.?[0 .. cmd_line.Length / 2];
82+
var args = try std.process.Args.Iterator.initAllocator(.{ .vector = cmd_line_slice }, allocator);
7783
defer args.deinit();
7884

7985
// Skip executable name
@@ -147,7 +153,9 @@ pub fn main() void {
147153
const crash_allocator = std.heap.page_allocator;
148154

149155
// Initialize the I/O subsystem
150-
var io_threaded = Io.Threaded.init(crash_allocator, .{});
156+
var io_threaded = Io.Threaded.init(crash_allocator, .{
157+
.environ = std.process.Environ.empty,
158+
});
151159
defer io_threaded.deinit();
152160
const io = io_threaded.io();
153161

src/client/renderer/buffer/ChunkBufferManager.zig

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,9 +295,12 @@ pub const ChunkBufferManager = struct {
295295
}
296296

297297
/// Begin a new frame (call before staging)
298+
/// NOTE: Does NOT process deferred frees - that's done by upload thread via advanceFrameAndProcessFrees()
299+
/// This only advances the frame counter for allocation tracking and begins the staging frame
298300
pub fn beginFrame(self: *Self, frame_fence: vk.VkFence) !void {
299-
self.frame_counter += 1;
300-
self.processDeferredFrees();
301+
// NOTE: frame_counter is also incremented by upload thread in advanceFrameAndProcessFrees()
302+
// This is intentional - main thread tracks its own frame for staging, upload thread tracks for deferred frees
303+
// The deferred free logic uses >= comparison so slightly out-of-sync counters are safe
301304
try self.staging.beginFrame(frame_fence);
302305
}
303306

src/server/Main.zig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ pub const Main = struct {
1616
defer _ = gpa.deinit();
1717
const allocator = gpa.allocator();
1818

19-
var args = try std.process.argsWithAllocator(allocator);
19+
const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
20+
const cmd_line_slice = cmd_line.Buffer.?[0 .. cmd_line.Length / 2];
21+
var args = try std.process.Args.Iterator.initAllocator(.{ .vector = cmd_line_slice }, allocator);
2022
defer args.deinit();
2123

2224
logger.info("Starting FarHorizons Server", .{});

src/shared/profiler.zig

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,123 @@ pub inline fn isConnected() bool {
193193
}
194194
return false;
195195
}
196+
197+
// =============================================================================
198+
// Memory Profiling
199+
// =============================================================================
200+
201+
/// Report a memory allocation to Tracy
202+
pub inline fn alloc(ptr: ?*anyopaque, size: usize) void {
203+
if (enabled) {
204+
c.___tracy_emit_memory_alloc(ptr, size, 0);
205+
}
206+
}
207+
208+
/// Report a memory allocation with name to Tracy
209+
pub inline fn allocNamed(ptr: ?*anyopaque, size: usize, comptime name: [:0]const u8) void {
210+
if (enabled) {
211+
c.___tracy_emit_memory_alloc_named(ptr, size, 0, name.ptr);
212+
}
213+
}
214+
215+
/// Report a memory free to Tracy
216+
pub inline fn free(ptr: ?*anyopaque) void {
217+
if (enabled) {
218+
c.___tracy_emit_memory_free(ptr, 0);
219+
}
220+
}
221+
222+
/// Report a memory free with name to Tracy
223+
pub inline fn freeNamed(ptr: ?*anyopaque, comptime name: [:0]const u8) void {
224+
if (enabled) {
225+
c.___tracy_emit_memory_free_named(ptr, 0, name.ptr);
226+
}
227+
}
228+
229+
/// A wrapper allocator that reports all allocations to Tracy
230+
/// Use this to wrap your main allocator for memory profiling
231+
pub fn TracyAllocator(comptime name: ?[:0]const u8) type {
232+
return struct {
233+
parent_allocator: std.mem.Allocator,
234+
235+
const Self = @This();
236+
237+
pub fn init(parent: std.mem.Allocator) Self {
238+
return .{ .parent_allocator = parent };
239+
}
240+
241+
pub fn allocator(self: *Self) std.mem.Allocator {
242+
return .{
243+
.ptr = self,
244+
.vtable = &.{
245+
.alloc = allocFn,
246+
.resize = resizeFn,
247+
.remap = remapFn,
248+
.free = freeFn,
249+
},
250+
};
251+
}
252+
253+
fn allocFn(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: usize) ?[*]u8 {
254+
const self: *Self = @ptrCast(@alignCast(ctx));
255+
const result = self.parent_allocator.rawAlloc(len, alignment, ret_addr);
256+
if (enabled and result != null) {
257+
if (name) |n| {
258+
c.___tracy_emit_memory_alloc_named(result, len, 0, n.ptr);
259+
} else {
260+
c.___tracy_emit_memory_alloc(result, len, 0);
261+
}
262+
}
263+
return result;
264+
}
265+
266+
fn resizeFn(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) bool {
267+
const self: *Self = @ptrCast(@alignCast(ctx));
268+
if (self.parent_allocator.rawResize(buf, alignment, new_len, ret_addr)) {
269+
if (enabled) {
270+
// Tracy doesn't have a resize, so report as free + alloc
271+
if (name) |n| {
272+
c.___tracy_emit_memory_free_named(buf.ptr, 0, n.ptr);
273+
c.___tracy_emit_memory_alloc_named(buf.ptr, new_len, 0, n.ptr);
274+
} else {
275+
c.___tracy_emit_memory_free(buf.ptr, 0);
276+
c.___tracy_emit_memory_alloc(buf.ptr, new_len, 0);
277+
}
278+
}
279+
return true;
280+
}
281+
return false;
282+
}
283+
284+
fn remapFn(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
285+
const self: *Self = @ptrCast(@alignCast(ctx));
286+
const old_ptr = memory.ptr;
287+
const result = self.parent_allocator.rawRemap(memory, alignment, new_len, ret_addr);
288+
if (enabled) {
289+
if (result) |new_ptr| {
290+
// Successful remap - report free of old, alloc of new
291+
if (name) |n| {
292+
c.___tracy_emit_memory_free_named(old_ptr, 0, n.ptr);
293+
c.___tracy_emit_memory_alloc_named(new_ptr, new_len, 0, n.ptr);
294+
} else {
295+
c.___tracy_emit_memory_free(old_ptr, 0);
296+
c.___tracy_emit_memory_alloc(new_ptr, new_len, 0);
297+
}
298+
}
299+
}
300+
return result;
301+
}
302+
303+
fn freeFn(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, ret_addr: usize) void {
304+
const self: *Self = @ptrCast(@alignCast(ctx));
305+
if (enabled) {
306+
if (name) |n| {
307+
c.___tracy_emit_memory_free_named(buf.ptr, 0, n.ptr);
308+
} else {
309+
c.___tracy_emit_memory_free(buf.ptr, 0);
310+
}
311+
}
312+
self.parent_allocator.rawFree(buf, alignment, ret_addr);
313+
}
314+
};
315+
}

0 commit comments

Comments
 (0)