From 09fc5168287af3fd79a61c2715d34bf02db09da6 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Sun, 9 Mar 2025 16:23:52 +0100 Subject: [PATCH] Move all the allocators to a separate file. They can now be imported with `main.heap` (analogous to `std.heap`) I also removed the unused BufferFallbackAllocator which was from a time where I was still experimenting with different allocators. --- src/Inventory.zig | 2 +- src/assets.zig | 6 +- src/blocks.zig | 8 +- src/entity.zig | 2 +- src/files.zig | 2 +- src/graphics.zig | 4 +- src/gui/components/ContinuousSlider.zig | 2 +- src/gui/gui.zig | 2 +- src/gui/windows/advanced_controls.zig | 4 +- src/gui/windows/controls.zig | 4 +- src/gui/windows/graphics.zig | 6 +- src/gui/windows/invite.zig | 2 +- src/gui/windows/multiplayer.zig | 2 +- src/gui/windows/save_creation.zig | 2 +- src/gui/windows/save_selection.zig | 6 +- src/gui/windows/sound.zig | 2 +- src/itemdrop.zig | 2 +- src/items.zig | 4 +- src/json.zig | 4 +- src/main.zig | 10 +- src/migrations.zig | 2 +- src/models.zig | 4 +- src/network.zig | 4 +- src/server/Entity.zig | 2 +- src/server/server.zig | 8 +- src/server/storage.zig | 4 +- src/server/terrain/CaveBiomeMap.zig | 4 +- src/server/terrain/CaveMap.zig | 2 +- src/server/terrain/ClimateMap.zig | 2 +- src/server/terrain/StructureMap.zig | 6 +- src/server/terrain/biomes.zig | 4 +- .../terrain/climategen/NoiseBasedVoronoi.zig | 2 +- src/server/terrain/noise/BlueNoise.zig | 2 +- src/server/terrain/noise/FractalNoise3D.zig | 2 +- src/server/terrain/noise/PerlinNoise.zig | 2 +- .../terrain/simple_structures/Boulder.zig | 2 +- .../terrain/simple_structures/FallenTree.zig | 2 +- .../terrain/simple_structures/FlowerPatch.zig | 2 +- .../terrain/simple_structures/GroundPatch.zig | 2 +- .../simple_structures/SimpleTreeModel.zig | 2 +- .../simple_structures/SimpleVegetation.zig | 2 +- .../terrain/simple_structures/Stalagmite.zig | 2 +- src/server/terrain/terrain.zig | 2 +- src/server/world.zig | 2 +- src/utils.zig | 545 +----------------- src/utils/heap.zig | 490 ++++++++++++++++ src/utils/list.zig | 2 +- src/zon.zig | 4 +- 48 files changed, 571 insertions(+), 614 deletions(-) create mode 100644 src/utils/heap.zig diff --git a/src/Inventory.zig b/src/Inventory.zig index d352ae39..b8f84ac8 100644 --- a/src/Inventory.zig +++ b/src/Inventory.zig @@ -7,7 +7,7 @@ const Item = main.items.Item; const ItemStack = main.items.ItemStack; const Tool = main.items.Tool; const utils = main.utils; -const NeverFailingAllocator = utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; diff --git a/src/assets.zig b/src/assets.zig index 5fbd445d..34fe110f 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -6,9 +6,9 @@ const migrations_zig = @import("migrations.zig"); const ZonElement = @import("zon.zig").ZonElement; const main = @import("main.zig"); const biomes_zig = main.server.terrain.biomes; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; -var arena: main.utils.NeverFailingArenaAllocator = undefined; +var arena: main.heap.NeverFailingArenaAllocator = undefined; var arenaAllocator: NeverFailingAllocator = undefined; var commonBlocks: std.StringHashMap(ZonElement) = undefined; var commonBlockMigrations: std.StringHashMap(ZonElement) = undefined; @@ -92,7 +92,7 @@ pub fn readAllZonFilesInAddons( }; defer dir.close(); - var defaultsArena: main.utils.NeverFailingArenaAllocator = .init(main.stackAllocator); + var defaultsArena: main.heap.NeverFailingArenaAllocator = .init(main.stackAllocator); defer defaultsArena.deinit(); const defaultsArenaAllocator = defaultsArena.allocator(); diff --git a/src/blocks.zig b/src/blocks.zig index 71c70b97..bf2d3271 100644 --- a/src/blocks.zig +++ b/src/blocks.zig @@ -43,7 +43,7 @@ pub const BlockTag = enum(u32) { return result; } - pub fn loadFromZon(_allocator: main.utils.NeverFailingAllocator, zon: ZonElement) []BlockTag { + pub fn loadFromZon(_allocator: main.heap.NeverFailingAllocator, zon: ZonElement) []BlockTag { const result = _allocator.alloc(BlockTag, zon.toSlice().len); for(zon.toSlice(), 0..) |tagZon, i| { result[i] = BlockTag.find(tagZon.as([]const u8, "incorrect")); @@ -56,7 +56,7 @@ pub const BlockTag = enum(u32) { } }; -var arena = main.utils.NeverFailingArenaAllocator.init(main.globalAllocator); +var arena = main.heap.NeverFailingArenaAllocator.init(main.globalAllocator); const allocator = arena.allocator(); pub const maxBlockCount: usize = 65536; // 16 bit limit @@ -447,7 +447,7 @@ pub const meshes = struct { // MARK: meshes var textureFogData: main.List(FogData) = undefined; pub var textureOcclusionData: main.List(bool) = undefined; - var arenaForWorld: main.utils.NeverFailingArenaAllocator = undefined; + var arenaForWorld: main.heap.NeverFailingArenaAllocator = undefined; pub var blockBreakingTextures: main.List(u16) = undefined; @@ -569,7 +569,7 @@ pub const meshes = struct { // MARK: meshes } } - fn extendedPath(_allocator: main.utils.NeverFailingAllocator, path: []const u8, ending: []const u8) []const u8 { + fn extendedPath(_allocator: main.heap.NeverFailingAllocator, path: []const u8, ending: []const u8) []const u8 { return std.fmt.allocPrint(_allocator.allocator, "{s}{s}", .{path, ending}) catch unreachable; } diff --git a/src/entity.zig b/src/entity.zig index 9da9e58f..dc660575 100644 --- a/src/entity.zig +++ b/src/entity.zig @@ -14,7 +14,7 @@ const Mat4f = vec.Mat4f; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; const Vec4f = vec.Vec4f; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const BinaryReader = main.utils.BinaryReader; diff --git a/src/files.zig b/src/files.zig index 022a4fb7..17911007 100644 --- a/src/files.zig +++ b/src/files.zig @@ -2,7 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const main = @import("root"); -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const ZonElement = main.ZonElement; pub fn read(allocator: NeverFailingAllocator, path: []const u8) ![]u8 { diff --git a/src/graphics.zig b/src/graphics.zig index d8fd922e..d684893d 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -27,7 +27,7 @@ const Vec3f = vec.Vec3f; const main = @import("main.zig"); const Window = main.Window; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const c = @cImport({ @cInclude("glad/glad.h"); @@ -1698,7 +1698,7 @@ pub const TextureArray = struct { // MARK: TextureArray for(0..maxLOD) |i| { c.glTexImage3D(c.GL_TEXTURE_2D_ARRAY, @intCast(i), c.GL_RGBA8, @max(0, maxWidth >> @intCast(i)), @max(0, maxHeight >> @intCast(i)), @intCast(images.len), 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, null); } - var arena = main.utils.NeverFailingArenaAllocator.init(main.stackAllocator); + var arena = main.heap.NeverFailingArenaAllocator.init(main.stackAllocator); defer arena.deinit(); const lodBuffer: [][]Color = arena.allocator().alloc([]Color, maxLOD); for(lodBuffer, 0..) |*buffer, i| { diff --git a/src/gui/components/ContinuousSlider.zig b/src/gui/components/ContinuousSlider.zig index 55e77607..a66bb715 100644 --- a/src/gui/components/ContinuousSlider.zig +++ b/src/gui/components/ContinuousSlider.zig @@ -9,7 +9,7 @@ const Texture = graphics.Texture; const random = main.random; const vec = main.vec; const Vec2f = vec.Vec2f; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const gui = @import("../gui.zig"); const GuiComponent = gui.GuiComponent; diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 2f503a6d..ece8fb1f 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -9,7 +9,7 @@ const vec = main.vec; const Vec2f = vec.Vec2f; const List = main.List; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const Button = @import("components/Button.zig"); const CheckBox = @import("components/CheckBox.zig"); diff --git a/src/gui/windows/advanced_controls.zig b/src/gui/windows/advanced_controls.zig index 2016a753..3e1baf93 100644 --- a/src/gui/windows/advanced_controls.zig +++ b/src/gui/windows/advanced_controls.zig @@ -24,7 +24,7 @@ fn delayCallback(newValue: f32) void { settings.save(); } -fn delayFormatter(allocator: main.utils.NeverFailingAllocator, value: f32) []const u8 { +fn delayFormatter(allocator: main.heap.NeverFailingAllocator, value: f32) []const u8 { return std.fmt.allocPrint(allocator.allocator, "#ffffffPlace/Break Delay: {d:.0} ms", .{value}) catch unreachable; } @@ -33,7 +33,7 @@ fn speedCallback(newValue: f32) void { settings.save(); } -fn speedFormatter(allocator: main.utils.NeverFailingAllocator, value: f32) []const u8 { +fn speedFormatter(allocator: main.heap.NeverFailingAllocator, value: f32) []const u8 { return std.fmt.allocPrint(allocator.allocator, "#ffffffPlace/Break Speed: {d:.0} ms", .{value}) catch unreachable; } diff --git a/src/gui/windows/controls.zig b/src/gui/windows/controls.zig index 8c77ea67..2168e9a6 100644 --- a/src/gui/windows/controls.zig +++ b/src/gui/windows/controls.zig @@ -59,11 +59,11 @@ fn updateDeadzone(deadzone: f32) void { main.settings.controllerAxisDeadzone = deadzone; } -fn deadzoneFormatter(allocator: main.utils.NeverFailingAllocator, value: f32) []const u8 { +fn deadzoneFormatter(allocator: main.heap.NeverFailingAllocator, value: f32) []const u8 { return std.fmt.allocPrint(allocator.allocator, "Deadzone: {d:.0}%", .{value*100}) catch unreachable; } -fn sensitivityFormatter(allocator: main.utils.NeverFailingAllocator, value: f32) []const u8 { +fn sensitivityFormatter(allocator: main.heap.NeverFailingAllocator, value: f32) []const u8 { return std.fmt.allocPrint(allocator.allocator, "{s} Sensitivity: {d:.0}%", .{if(editingKeyboard) "Mouse" else "Controller", value*100}) catch unreachable; } diff --git a/src/gui/windows/graphics.zig b/src/gui/windows/graphics.zig index 5ec47f4d..4e210df4 100644 --- a/src/gui/windows/graphics.zig +++ b/src/gui/windows/graphics.zig @@ -37,7 +37,7 @@ fn fpsCapRound(newValue: f32) ?u32 { } } -fn fpsCapFormatter(allocator: main.utils.NeverFailingAllocator, value: f32) []const u8 { +fn fpsCapFormatter(allocator: main.heap.NeverFailingAllocator, value: f32) []const u8 { const cap = fpsCapRound(value); if(cap == null) return allocator.dupe(u8, "#ffffffFPS: Unlimited"); @@ -70,11 +70,11 @@ fn fovCallback(newValue: f32) void { main.Window.GLFWCallbacks.framebufferSize(undefined, main.Window.width, main.Window.height); } -fn fovFormatter(allocator: main.utils.NeverFailingAllocator, value: f32) []const u8 { +fn fovFormatter(allocator: main.heap.NeverFailingAllocator, value: f32) []const u8 { return std.fmt.allocPrint(allocator.allocator, "#ffffffField Of View: {d:.0}°", .{value}) catch unreachable; } -fn lodDistanceFormatter(allocator: main.utils.NeverFailingAllocator, value: f32) []const u8 { +fn lodDistanceFormatter(allocator: main.heap.NeverFailingAllocator, value: f32) []const u8 { return std.fmt.allocPrint(allocator.allocator, "#ffffffOpaque leaves distance: {d:.0}", .{@round(value)}) catch unreachable; } diff --git a/src/gui/windows/invite.zig b/src/gui/windows/invite.zig index bbcc337a..47487cfa 100644 --- a/src/gui/windows/invite.zig +++ b/src/gui/windows/invite.zig @@ -35,7 +35,7 @@ fn discoverIpAddress() void { } fn discoverIpAddressFromNewThread() void { - var sta = main.utils.StackAllocator.init(main.globalAllocator, 1 << 23); + var sta = main.heap.StackAllocator.init(main.globalAllocator, 1 << 23); defer sta.deinit(); main.stackAllocator = sta.allocator(); diff --git a/src/gui/windows/multiplayer.zig b/src/gui/windows/multiplayer.zig index a643ce89..cc0fe2d0 100644 --- a/src/gui/windows/multiplayer.zig +++ b/src/gui/windows/multiplayer.zig @@ -39,7 +39,7 @@ fn discoverIpAddress() void { } fn discoverIpAddressFromNewThread() void { - var sta = main.utils.StackAllocator.init(main.globalAllocator, 1 << 23); + var sta = main.heap.StackAllocator.init(main.globalAllocator, 1 << 23); defer sta.deinit(); main.stackAllocator = sta.allocator(); diff --git a/src/gui/windows/save_creation.zig b/src/gui/windows/save_creation.zig index ddc08514..fd1a6062 100644 --- a/src/gui/windows/save_creation.zig +++ b/src/gui/windows/save_creation.zig @@ -4,7 +4,7 @@ const main = @import("root"); const ConnectionManager = main.network.ConnectionManager; const settings = main.settings; const Vec2f = main.vec.Vec2f; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const gui = @import("../gui.zig"); const GuiComponent = gui.GuiComponent; diff --git a/src/gui/windows/save_selection.zig b/src/gui/windows/save_selection.zig index 9e604b21..044393fa 100644 --- a/src/gui/windows/save_selection.zig +++ b/src/gui/windows/save_selection.zig @@ -4,7 +4,7 @@ const main = @import("root"); const ConnectionManager = main.network.ConnectionManager; const settings = main.settings; const Vec2f = main.vec.Vec2f; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const Texture = main.graphics.Texture; const gui = @import("../gui.zig"); @@ -22,7 +22,7 @@ pub var window = GuiWindow{ const padding: f32 = 8; const width: f32 = 128; -var buttonNameArena: main.utils.NeverFailingArenaAllocator = undefined; +var buttonNameArena: main.heap.NeverFailingArenaAllocator = undefined; pub var needsUpdate: bool = false; @@ -127,7 +127,7 @@ pub fn update() void { } pub fn onOpen() void { - buttonNameArena = main.utils.NeverFailingArenaAllocator.init(main.globalAllocator); + buttonNameArena = main.heap.NeverFailingArenaAllocator.init(main.globalAllocator); const list = VerticalList.init(.{padding, 16 + padding}, 300, 8); list.add(Label.init(.{0, 0}, width, "**Select World**", .center)); list.add(Button.initText(.{0, 0}, 128, "Create New World", gui.openWindowCallback("save_creation"))); diff --git a/src/gui/windows/sound.zig b/src/gui/windows/sound.zig index f62d6fc5..219cf231 100644 --- a/src/gui/windows/sound.zig +++ b/src/gui/windows/sound.zig @@ -3,7 +3,7 @@ const std = @import("std"); const main = @import("root"); const settings = main.settings; const Vec2f = main.vec.Vec2f; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const gui = @import("../gui.zig"); const GuiComponent = gui.GuiComponent; diff --git a/src/itemdrop.zig b/src/itemdrop.zig index fdf7cc9d..61ddf77e 100644 --- a/src/itemdrop.zig +++ b/src/itemdrop.zig @@ -20,7 +20,7 @@ const Mat4f = vec.Mat4f; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; const Vec3i = vec.Vec3i; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const ItemDrop = struct { // MARK: ItemDrop pos: Vec3d, diff --git a/src/items.zig b/src/items.zig index 68931830..cf4833c4 100644 --- a/src/items.zig +++ b/src/items.zig @@ -14,7 +14,7 @@ const Vec2f = vec.Vec2f; const Vec2i = vec.Vec2i; const Vec3i = vec.Vec3i; const Vec3f = vec.Vec3f; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const modifierList = @import("tool/modifiers/_list.zig"); @@ -747,7 +747,7 @@ pub const Recipe = struct { // MARK: Recipe cachedInventory: ?Inventory = null, }; -var arena: main.utils.NeverFailingArenaAllocator = undefined; +var arena: main.heap.NeverFailingArenaAllocator = undefined; var toolTypes: std.StringHashMap(ToolType) = undefined; var reverseIndices: std.StringHashMap(*BaseItem) = undefined; var modifiers: std.StringHashMap(*const Modifier.VTable) = undefined; diff --git a/src/json.zig b/src/json.zig index cdbe8320..fbc5ea95 100644 --- a/src/json.zig +++ b/src/json.zig @@ -1,7 +1,7 @@ const std = @import("std"); const main = @import("main.zig"); -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const List = main.List; const JsonType = enum(u8) { @@ -180,7 +180,7 @@ pub const JsonElement = union(JsonType) { // MARK: JsonElement }, .vector => { const len = @typeInfo(@TypeOf(value)).vector.len; - const result = initArray(main.utils.NeverFailingAllocator{.allocator = allocator, .IAssertThatTheProvidedAllocatorCantFail = {}}); + const result = initArray(main.heap.NeverFailingAllocator{.allocator = allocator, .IAssertThatTheProvidedAllocatorCantFail = {}}); result.JsonArray.ensureCapacity(len); inline for(0..len) |i| { result.JsonArray.appendAssumeCapacity(createElementFromRandomType(value[i], allocator)); diff --git a/src/main.zig b/src/main.zig index 14803437..2fb4903c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -27,6 +27,8 @@ pub const ZonElement = @import("zon.zig").ZonElement; pub const Window = @import("graphics/Window.zig"); +pub const heap = @import("utils/heap.zig"); + pub const List = @import("utils/list.zig").List; pub const ListUnmanaged = @import("utils/list.zig").ListUnmanaged; pub const VirtualList = @import("utils/list.zig").VirtualList; @@ -36,11 +38,11 @@ const file_monitor = utils.file_monitor; const Vec2f = vec.Vec2f; const Vec3d = vec.Vec3d; -pub threadlocal var stackAllocator: utils.NeverFailingAllocator = undefined; +pub threadlocal var stackAllocator: heap.NeverFailingAllocator = undefined; pub threadlocal var seed: u64 = undefined; var global_gpa = std.heap.GeneralPurposeAllocator(.{.thread_safe = true}){}; -var handled_gpa = utils.ErrorHandlingAllocator.init(global_gpa.allocator()); -pub const globalAllocator: utils.NeverFailingAllocator = handled_gpa.allocator(); +var handled_gpa = heap.ErrorHandlingAllocator.init(global_gpa.allocator()); +pub const globalAllocator: heap.NeverFailingAllocator = handled_gpa.allocator(); pub var threadPool: *utils.ThreadPool = undefined; fn cacheStringImpl(comptime len: usize, comptime str: [len]u8) []const u8 { @@ -522,7 +524,7 @@ pub fn main() void { // MARK: main() defer if(global_gpa.deinit() == .leak) { std.log.err("Memory leak", .{}); }; - var sta = utils.StackAllocator.init(globalAllocator, 1 << 23); + var sta = heap.StackAllocator.init(globalAllocator, 1 << 23); defer sta.deinit(); stackAllocator = sta.allocator(); diff --git a/src/migrations.zig b/src/migrations.zig index 3c70b561..738bfdf1 100644 --- a/src/migrations.zig +++ b/src/migrations.zig @@ -4,7 +4,7 @@ const main = @import("main.zig"); const ZonElement = @import("zon.zig").ZonElement; const Palette = @import("assets.zig").Palette; -var arenaAllocator = main.utils.NeverFailingArenaAllocator.init(main.globalAllocator); +var arenaAllocator = main.heap.NeverFailingArenaAllocator.init(main.globalAllocator); const migrationAllocator = arenaAllocator.allocator(); var blockMigrations: std.StringHashMap([]const u8) = .init(migrationAllocator.allocator); diff --git a/src/models.zig b/src/models.zig index 4360a4d6..606c00c2 100644 --- a/src/models.zig +++ b/src/models.zig @@ -10,7 +10,7 @@ const Vec3f = vec.Vec3f; const Vec2f = vec.Vec2f; const Mat4f = vec.Mat4f; const FaceData = main.renderer.chunk_meshing.FaceData; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; var quadSSBO: graphics.SSBO = undefined; @@ -199,7 +199,7 @@ pub const Model = struct { return Model.init(quadInfos); } - pub fn loadRawModelDataFromObj(allocator: main.utils.NeverFailingAllocator, data: []const u8) []QuadInfo { + pub fn loadRawModelDataFromObj(allocator: main.heap.NeverFailingAllocator, data: []const u8) []QuadInfo { var vertices = main.List(Vec3f).init(main.stackAllocator); defer vertices.deinit(); diff --git a/src/network.zig b/src/network.zig index e80e71c3..6f50d2e9 100644 --- a/src/network.zig +++ b/src/network.zig @@ -19,7 +19,7 @@ const vec = @import("vec.zig"); const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; const Vec3i = vec.Vec3i; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const networkEndian: std.builtin.Endian = .big; @@ -559,7 +559,7 @@ pub const ConnectionManager = struct { // MARK: ConnectionManager pub fn run(self: *ConnectionManager) void { self.threadId = std.Thread.getCurrentId(); - var sta = utils.StackAllocator.init(main.globalAllocator, 1 << 23); + var sta = main.heap.StackAllocator.init(main.globalAllocator, 1 << 23); defer sta.deinit(); main.stackAllocator = sta.allocator(); diff --git a/src/server/Entity.zig b/src/server/Entity.zig index 9876ae23..0f7c2a66 100644 --- a/src/server/Entity.zig +++ b/src/server/Entity.zig @@ -5,7 +5,7 @@ const ZonElement = main.ZonElement; const vec = main.vec; const Vec3f = vec.Vec3f; const Vec3d = vec.Vec3d; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pos: Vec3d = .{0, 0, 0}, vel: Vec3d = .{0, 0, 0}, diff --git a/src/server/server.zig b/src/server/server.zig index 1f0eb104..aaae697c 100644 --- a/src/server/server.zig +++ b/src/server/server.zig @@ -293,7 +293,7 @@ fn deinit() void { command.deinit(); } -pub fn getUserListAndIncreaseRefCount(allocator: utils.NeverFailingAllocator) []*User { +pub fn getUserListAndIncreaseRefCount(allocator: main.heap.NeverFailingAllocator) []*User { userMutex.lock(); defer userMutex.unlock(); const result = allocator.dupe(*User, users.items); @@ -303,14 +303,14 @@ pub fn getUserListAndIncreaseRefCount(allocator: utils.NeverFailingAllocator) [] return result; } -pub fn freeUserListAndDecreaseRefCount(allocator: utils.NeverFailingAllocator, list: []*User) void { +pub fn freeUserListAndDecreaseRefCount(allocator: main.heap.NeverFailingAllocator, list: []*User) void { for(list) |user| { user.decreaseRefCount(); } allocator.free(list); } -fn getInitialEntityList(allocator: utils.NeverFailingAllocator) []const u8 { +fn getInitialEntityList(allocator: main.heap.NeverFailingAllocator) []const u8 { // Send the entity updates: var initialList: []const u8 = undefined; const list = main.ZonElement.initArray(main.stackAllocator); @@ -369,7 +369,7 @@ fn update() void { // MARK: update() } pub fn start(name: []const u8, port: ?u16) void { - var sta = utils.StackAllocator.init(main.globalAllocator, 1 << 23); + var sta = main.heap.StackAllocator.init(main.globalAllocator, 1 << 23); defer sta.deinit(); main.stackAllocator = sta.allocator(); std.debug.assert(!running.load(.monotonic)); // There can only be one server. diff --git a/src/server/storage.zig b/src/server/storage.zig index aa179999..de06350f 100644 --- a/src/server/storage.zig +++ b/src/server/storage.zig @@ -170,7 +170,7 @@ pub const RegionFile = struct { // MARK: RegionFile } } - pub fn getChunk(self: *RegionFile, allocator: main.utils.NeverFailingAllocator, relX: usize, relY: usize, relZ: usize) ?[]const u8 { + pub fn getChunk(self: *RegionFile, allocator: main.heap.NeverFailingAllocator, relX: usize, relY: usize, relZ: usize) ?[]const u8 { self.mutex.lock(); defer self.mutex.unlock(); const index = getIndex(relX, relY, relZ); @@ -259,7 +259,7 @@ pub const ChunkCompression = struct { // MARK: ChunkCompression deflate_with_8bit_palette = 3, _, }; - pub fn compressChunk(allocator: main.utils.NeverFailingAllocator, ch: *chunk.Chunk, allowLossy: bool) []const u8 { + pub fn compressChunk(allocator: main.heap.NeverFailingAllocator, ch: *chunk.Chunk, allowLossy: bool) []const u8 { if(ch.data.paletteLength == 1) { var writer = BinaryWriter.initCapacity(allocator, .big, @sizeOf(CompressionAlgo) + @sizeOf(u32)); diff --git a/src/server/terrain/CaveBiomeMap.zig b/src/server/terrain/CaveBiomeMap.zig index 57289ffc..a1dfbf8b 100644 --- a/src/server/terrain/CaveBiomeMap.zig +++ b/src/server/terrain/CaveBiomeMap.zig @@ -8,7 +8,7 @@ const ChunkPosition = main.chunk.ChunkPosition; const ZonElement = main.ZonElement; const vec = main.vec; const Vec3i = vec.Vec3i; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const terrain = @import("terrain.zig"); const TerrainGenerationProfile = terrain.TerrainGenerationProfile; @@ -147,7 +147,7 @@ pub const InterpolatableCaveBiomeMapView = struct { // MARK: InterpolatableCaveB width: i32, allocator: NeverFailingAllocator, - pub fn init(allocator: main.utils.NeverFailingAllocator, pos: ChunkPosition, width: u31, margin: u31) InterpolatableCaveBiomeMapView { + pub fn init(allocator: main.heap.NeverFailingAllocator, pos: ChunkPosition, width: u31, margin: u31) InterpolatableCaveBiomeMapView { const center = Vec3i{ pos.wx +% width/2, pos.wy +% width/2, diff --git a/src/server/terrain/CaveMap.zig b/src/server/terrain/CaveMap.zig index 69b7ba81..97c00d14 100644 --- a/src/server/terrain/CaveMap.zig +++ b/src/server/terrain/CaveMap.zig @@ -6,7 +6,7 @@ const ServerChunk = main.chunk.ServerChunk; const ChunkPosition = main.chunk.ChunkPosition; const Cache = main.utils.Cache; const ZonElement = main.ZonElement; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const terrain = @import("terrain.zig"); const TerrainGenerationProfile = terrain.TerrainGenerationProfile; diff --git a/src/server/terrain/ClimateMap.zig b/src/server/terrain/ClimateMap.zig index 33e5250a..ac7615a8 100644 --- a/src/server/terrain/ClimateMap.zig +++ b/src/server/terrain/ClimateMap.zig @@ -9,7 +9,7 @@ const terrain = main.server.terrain; const TerrainGenerationProfile = terrain.TerrainGenerationProfile; const Biome = terrain.biomes.Biome; const MapFragment = terrain.SurfaceMap.MapFragment; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const BiomeSample = struct { biome: *const Biome, diff --git a/src/server/terrain/StructureMap.zig b/src/server/terrain/StructureMap.zig index aa682383..3e6e1d18 100644 --- a/src/server/terrain/StructureMap.zig +++ b/src/server/terrain/StructureMap.zig @@ -6,7 +6,7 @@ const ServerChunk = main.chunk.ServerChunk; const ChunkPosition = main.chunk.ChunkPosition; const Cache = main.utils.Cache; const ZonElement = main.ZonElement; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const vec = main.vec; const Vec3i = vec.Vec3i; @@ -41,8 +41,8 @@ pub const StructureMapFragment = struct { pos: ChunkPosition, voxelShift: u5, refCount: Atomic(u16) = .init(0), - arena: main.utils.NeverFailingArenaAllocator, - allocator: main.utils.NeverFailingAllocator, + arena: main.heap.NeverFailingArenaAllocator, + allocator: main.heap.NeverFailingAllocator, tempData: struct { lists: *[chunkedSize*chunkedSize*chunkedSize]main.ListUnmanaged(Structure), diff --git a/src/server/terrain/biomes.zig b/src/server/terrain/biomes.zig index bd016b4c..88cece59 100644 --- a/src/server/terrain/biomes.zig +++ b/src/server/terrain/biomes.zig @@ -5,7 +5,7 @@ const blocks = main.blocks; const ServerChunk = main.chunk.ServerChunk; const ZonElement = main.ZonElement; const terrain = main.server.terrain; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const vec = @import("main.vec"); const Vec3f = main.vec.Vec3f; const Vec3d = main.vec.Vec3d; @@ -51,7 +51,7 @@ pub const SimpleStructureModel = struct { // MARK: SimpleStructureModel } var modelRegistry: std.StringHashMapUnmanaged(VTable) = .{}; - var arena: main.utils.NeverFailingArenaAllocator = .init(main.globalAllocator); + var arena: main.heap.NeverFailingArenaAllocator = .init(main.globalAllocator); pub fn reset() void { std.debug.assert(arena.reset(.free_all)); diff --git a/src/server/terrain/climategen/NoiseBasedVoronoi.zig b/src/server/terrain/climategen/NoiseBasedVoronoi.zig index 19c66415..ce01e576 100644 --- a/src/server/terrain/climategen/NoiseBasedVoronoi.zig +++ b/src/server/terrain/climategen/NoiseBasedVoronoi.zig @@ -13,7 +13,7 @@ const vec = main.vec; const Vec2i = vec.Vec2i; const Vec2f = vec.Vec2f; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; // Generates the climate map using a fluidynamics simulation, with a circular heat distribution. diff --git a/src/server/terrain/noise/BlueNoise.zig b/src/server/terrain/noise/BlueNoise.zig index d70e7174..fc272583 100644 --- a/src/server/terrain/noise/BlueNoise.zig +++ b/src/server/terrain/noise/BlueNoise.zig @@ -3,7 +3,7 @@ const std = @import("std"); const main = @import("root"); const random = main.random; const Array2D = main.utils.Array2D; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const sizeShift = 7; // TODO: Increase back to 10 once this is no longer impacting loading time. const size = 1 << sizeShift; diff --git a/src/server/terrain/noise/FractalNoise3D.zig b/src/server/terrain/noise/FractalNoise3D.zig index a845edce..c3f8576e 100644 --- a/src/server/terrain/noise/FractalNoise3D.zig +++ b/src/server/terrain/noise/FractalNoise3D.zig @@ -4,7 +4,7 @@ const main = @import("root"); const Array3D = main.utils.Array3D; const ChunkPosition = main.chunk.ChunkPosition; const random = main.random; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const FractalNoise3D = @This(); diff --git a/src/server/terrain/noise/PerlinNoise.zig b/src/server/terrain/noise/PerlinNoise.zig index f41556b1..35ce1319 100644 --- a/src/server/terrain/noise/PerlinNoise.zig +++ b/src/server/terrain/noise/PerlinNoise.zig @@ -3,7 +3,7 @@ const std = @import("std"); const main = @import("root"); const Array2D = main.utils.Array2D; const random = main.random; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; // TODO: Simplify with Vec2f and Vec2i. diff --git a/src/server/terrain/simple_structures/Boulder.zig b/src/server/terrain/simple_structures/Boulder.zig index 6dae205b..3973e1bf 100644 --- a/src/server/terrain/simple_structures/Boulder.zig +++ b/src/server/terrain/simple_structures/Boulder.zig @@ -9,7 +9,7 @@ const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; const Vec3i = vec.Vec3i; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const id = "cubyz:boulder"; diff --git a/src/server/terrain/simple_structures/FallenTree.zig b/src/server/terrain/simple_structures/FallenTree.zig index a53ab38f..22c206b5 100644 --- a/src/server/terrain/simple_structures/FallenTree.zig +++ b/src/server/terrain/simple_structures/FallenTree.zig @@ -9,7 +9,7 @@ const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; const Vec3i = vec.Vec3i; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const id = "cubyz:fallen_tree"; diff --git a/src/server/terrain/simple_structures/FlowerPatch.zig b/src/server/terrain/simple_structures/FlowerPatch.zig index 95dd2654..7c28a4a5 100644 --- a/src/server/terrain/simple_structures/FlowerPatch.zig +++ b/src/server/terrain/simple_structures/FlowerPatch.zig @@ -9,7 +9,7 @@ const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; const Vec3i = vec.Vec3i; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const id = "cubyz:flower_patch"; diff --git a/src/server/terrain/simple_structures/GroundPatch.zig b/src/server/terrain/simple_structures/GroundPatch.zig index ff6960c2..552c5a34 100644 --- a/src/server/terrain/simple_structures/GroundPatch.zig +++ b/src/server/terrain/simple_structures/GroundPatch.zig @@ -9,7 +9,7 @@ const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; const Vec3i = vec.Vec3i; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const id = "cubyz:ground_patch"; diff --git a/src/server/terrain/simple_structures/SimpleTreeModel.zig b/src/server/terrain/simple_structures/SimpleTreeModel.zig index b03483ea..7803e9fc 100644 --- a/src/server/terrain/simple_structures/SimpleTreeModel.zig +++ b/src/server/terrain/simple_structures/SimpleTreeModel.zig @@ -9,7 +9,7 @@ const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; const Vec3i = vec.Vec3i; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const id = "cubyz:simple_tree"; diff --git a/src/server/terrain/simple_structures/SimpleVegetation.zig b/src/server/terrain/simple_structures/SimpleVegetation.zig index 1d64e0ed..5657c735 100644 --- a/src/server/terrain/simple_structures/SimpleVegetation.zig +++ b/src/server/terrain/simple_structures/SimpleVegetation.zig @@ -9,7 +9,7 @@ const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; const Vec3i = vec.Vec3i; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const id = "cubyz:simple_vegetation"; diff --git a/src/server/terrain/simple_structures/Stalagmite.zig b/src/server/terrain/simple_structures/Stalagmite.zig index d66a5ec8..238396f6 100644 --- a/src/server/terrain/simple_structures/Stalagmite.zig +++ b/src/server/terrain/simple_structures/Stalagmite.zig @@ -9,7 +9,7 @@ const vec = main.vec; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; const Vec3i = vec.Vec3i; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const id = "cubyz:stalagmite"; diff --git a/src/server/terrain/terrain.zig b/src/server/terrain/terrain.zig index 1e30f8bc..dcc3a6e7 100644 --- a/src/server/terrain/terrain.zig +++ b/src/server/terrain/terrain.zig @@ -2,7 +2,7 @@ const std = @import("std"); const main = @import("root"); const ZonElement = main.ZonElement; -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const biomes = @import("biomes.zig"); pub const noise = @import("noise/noise.zig"); diff --git a/src/server/world.zig b/src/server/world.zig index 32e101f0..f633ed15 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -497,7 +497,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld self.itemDropManager.init(main.globalAllocator, self, self.gravity); errdefer self.itemDropManager.deinit(); - var loadArena = main.utils.NeverFailingArenaAllocator.init(main.stackAllocator); + var loadArena = main.heap.NeverFailingArenaAllocator.init(main.stackAllocator); defer loadArena.deinit(); const arenaAllocator = loadArena.allocator(); var buf: [32768]u8 = undefined; diff --git a/src/utils.zig b/src/utils.zig index 1cfbec49..91ad527e 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -4,6 +4,7 @@ const Atomic = std.atomic.Value; const builtin = @import("builtin"); const main = @import("main.zig"); +const NeverFailingAllocator = main.heap.NeverFailingAllocator; pub const file_monitor = @import("utils/file_monitor.zig"); @@ -424,542 +425,6 @@ pub fn ConcurrentQueue(comptime T: type) type { // MARK: ConcurrentQueue }; } -/// Allows for stack-like allocations in a fast and safe way. -/// It is safe in the sense that a regular allocator will be used when the buffer is full. -pub const StackAllocator = struct { // MARK: StackAllocator - const AllocationTrailer = packed struct {wasFreed: bool, previousAllocationTrailer: u31}; - backingAllocator: NeverFailingAllocator, - buffer: []align(4096) u8, - index: usize, - - pub fn init(backingAllocator: NeverFailingAllocator, size: u31) StackAllocator { - return .{ - .backingAllocator = backingAllocator, - .buffer = backingAllocator.alignedAlloc(u8, 4096, size), - .index = 0, - }; - } - - pub fn deinit(self: StackAllocator) void { - if(self.index != 0) { - std.log.err("Memory leak in Stack Allocator", .{}); - } - self.backingAllocator.free(self.buffer); - } - - pub fn allocator(self: *StackAllocator) NeverFailingAllocator { - return .{ - .allocator = .{ - .vtable = &.{ - .alloc = &alloc, - .resize = &resize, - .remap = &remap, - .free = &free, - }, - .ptr = self, - }, - .IAssertThatTheProvidedAllocatorCantFail = {}, - }; - } - - fn isInsideBuffer(self: *StackAllocator, buf: []u8) bool { - const bufferStart = @intFromPtr(self.buffer.ptr); - const bufferEnd = bufferStart + self.buffer.len; - const compare = @intFromPtr(buf.ptr); - return compare >= bufferStart and compare < bufferEnd; - } - - fn indexInBuffer(self: *StackAllocator, buf: []u8) usize { - const bufferStart = @intFromPtr(self.buffer.ptr); - const compare = @intFromPtr(buf.ptr); - return compare - bufferStart; - } - - fn getTrueAllocationEnd(start: usize, len: usize) usize { - const trailerStart = std.mem.alignForward(usize, start + len, @alignOf(AllocationTrailer)); - return trailerStart + @sizeOf(AllocationTrailer); - } - - fn getTrailerBefore(self: *StackAllocator, end: usize) *AllocationTrailer { - const trailerStart = end - @sizeOf(AllocationTrailer); - return @ptrCast(@alignCast(self.buffer[trailerStart..].ptr)); - } - - fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: usize) ?[*]u8 { - const self: *StackAllocator = @ptrCast(@alignCast(ctx)); - const start = std.mem.alignForward(usize, self.index, @as(usize, 1) << @intCast(@intFromEnum(alignment))); - const end = getTrueAllocationEnd(start, len); - if(end >= self.buffer.len) return self.backingAllocator.rawAlloc(len, alignment, ret_addr); - const trailer = self.getTrailerBefore(end); - trailer.* = .{.wasFreed = false, .previousAllocationTrailer = @intCast(self.index)}; - self.index = end; - return self.buffer.ptr + start; - } - - fn resize(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) bool { - const self: *StackAllocator = @ptrCast(@alignCast(ctx)); - if(self.isInsideBuffer(memory)) { - const start = self.indexInBuffer(memory); - const end = getTrueAllocationEnd(start, memory.len); - if(end != self.index) return false; - const newEnd = getTrueAllocationEnd(start, new_len); - if(newEnd >= self.buffer.len) return false; - - const trailer = self.getTrailerBefore(end); - std.debug.assert(!trailer.wasFreed); - const newTrailer = self.getTrailerBefore(newEnd); - - newTrailer.* = .{.wasFreed = false, .previousAllocationTrailer = trailer.previousAllocationTrailer}; - self.index = newEnd; - return true; - } else { - return self.backingAllocator.rawResize(memory, alignment, new_len, ret_addr); - } - } - - fn remap(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 { - if(resize(ctx, memory, alignment, new_len, ret_addr)) return memory.ptr; - return null; - } - - fn free(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, ret_addr: usize) void { - const self: *StackAllocator = @ptrCast(@alignCast(ctx)); - if(self.isInsideBuffer(memory)) { - const start = self.indexInBuffer(memory); - const end = getTrueAllocationEnd(start, memory.len); - const trailer = self.getTrailerBefore(end); - std.debug.assert(!trailer.wasFreed); // Double Free - - if(end == self.index) { - self.index = trailer.previousAllocationTrailer; - if(self.index != 0) { - var previousTrailer = self.getTrailerBefore(trailer.previousAllocationTrailer); - while(previousTrailer.wasFreed) { - self.index = previousTrailer.previousAllocationTrailer; - if(self.index == 0) break; - previousTrailer = self.getTrailerBefore(previousTrailer.previousAllocationTrailer); - } - } - } else { - trailer.wasFreed = true; - } - } else { - self.backingAllocator.rawFree(memory, alignment, ret_addr); - } - } -}; - -/// An allocator that handles OutOfMemory situations by panicing or freeing memory(TODO), making it safe to ignore errors. -pub const ErrorHandlingAllocator = struct { // MARK: ErrorHandlingAllocator - backingAllocator: Allocator, - - pub fn init(backingAllocator: Allocator) ErrorHandlingAllocator { - return .{ - .backingAllocator = backingAllocator, - }; - } - - pub fn allocator(self: *ErrorHandlingAllocator) NeverFailingAllocator { - return .{ - .allocator = .{ - .vtable = &.{ - .alloc = &alloc, - .resize = &resize, - .remap = &remap, - .free = &free, - }, - .ptr = self, - }, - .IAssertThatTheProvidedAllocatorCantFail = {}, - }; - } - - fn handleError() noreturn { - @panic("Out Of Memory. Please download more RAM, reduce the render distance, or close some of your 100 browser tabs."); - } - - /// Return a pointer to `len` bytes with specified `alignment`, or return - /// `null` indicating the allocation failed. - /// - /// `ret_addr` is optionally provided as the first return address of the - /// allocation call stack. If the value is `0` it means no return address - /// has been provided. - fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: usize) ?[*]u8 { - const self: *ErrorHandlingAllocator = @ptrCast(@alignCast(ctx)); - return self.backingAllocator.rawAlloc(len, alignment, ret_addr) orelse handleError(); - } - - /// Attempt to expand or shrink memory in place. - /// - /// `memory.len` must equal the length requested from the most recent - /// successful call to `alloc`, `resize`, or `remap`. `alignment` must - /// equal the same value that was passed as the `alignment` parameter to - /// the original `alloc` call. - /// - /// A result of `true` indicates the resize was successful and the - /// allocation now has the same address but a size of `new_len`. `false` - /// indicates the resize could not be completed without moving the - /// allocation to a different address. - /// - /// `new_len` must be greater than zero. - /// - /// `ret_addr` is optionally provided as the first return address of the - /// allocation call stack. If the value is `0` it means no return address - /// has been provided. - fn resize(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) bool { - const self: *ErrorHandlingAllocator = @ptrCast(@alignCast(ctx)); - return self.backingAllocator.rawResize(memory, alignment, new_len, ret_addr); - } - - /// Attempt to expand or shrink memory, allowing relocation. - /// - /// `memory.len` must equal the length requested from the most recent - /// successful call to `alloc`, `resize`, or `remap`. `alignment` must - /// equal the same value that was passed as the `alignment` parameter to - /// the original `alloc` call. - /// - /// A non-`null` return value indicates the resize was successful. The - /// allocation may have same address, or may have been relocated. In either - /// case, the allocation now has size of `new_len`. A `null` return value - /// indicates that the resize would be equivalent to allocating new memory, - /// copying the bytes from the old memory, and then freeing the old memory. - /// In such case, it is more efficient for the caller to perform the copy. - /// - /// `new_len` must be greater than zero. - /// - /// `ret_addr` is optionally provided as the first return address of the - /// allocation call stack. If the value is `0` it means no return address - /// has been provided. - fn remap(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 { - const self: *ErrorHandlingAllocator = @ptrCast(@alignCast(ctx)); - return self.backingAllocator.rawRemap(memory, alignment, new_len, ret_addr); - } - - /// Free and invalidate a region of memory. - /// - /// `memory.len` must equal the length requested from the most recent - /// successful call to `alloc`, `resize`, or `remap`. `alignment` must - /// equal the same value that was passed as the `alignment` parameter to - /// the original `alloc` call. - /// - /// `ret_addr` is optionally provided as the first return address of the - /// allocation call stack. If the value is `0` it means no return address - /// has been provided. - fn free(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, ret_addr: usize) void { - const self: *ErrorHandlingAllocator = @ptrCast(@alignCast(ctx)); - self.backingAllocator.rawFree(memory, alignment, ret_addr); - } -}; - -/// An allocator interface signaling that you can use -pub const NeverFailingAllocator = struct { // MARK: NeverFailingAllocator - allocator: Allocator, - IAssertThatTheProvidedAllocatorCantFail: void, - - const Alignment = std.mem.Alignment; - const math = std.math; - - /// This function is not intended to be called except from within the - /// implementation of an `Allocator`. - pub inline fn rawAlloc(a: NeverFailingAllocator, len: usize, alignment: Alignment, ret_addr: usize) ?[*]u8 { - return a.allocator.vtable.alloc(a.allocator.ptr, len, alignment, ret_addr); - } - - /// This function is not intended to be called except from within the - /// implementation of an `Allocator`. - pub inline fn rawResize(a: NeverFailingAllocator, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) bool { - return a.allocator.vtable.resize(a.allocator.ptr, memory, alignment, new_len, ret_addr); - } - - /// This function is not intended to be called except from within the - /// implementation of an `Allocator`. - pub inline fn rawRemap(a: NeverFailingAllocator, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) ?[*]u8 { - return a.allocator.vtable.remap(a.allocator.ptr, memory, alignment, new_len, ret_addr); - } - - /// This function is not intended to be called except from within the - /// implementation of an `Allocator`. - pub inline fn rawFree(a: NeverFailingAllocator, memory: []u8, alignment: Alignment, ret_addr: usize) void { - return a.allocator.vtable.free(a.allocator.ptr, memory, alignment, ret_addr); - } - - /// Returns a pointer to undefined memory. - /// Call `destroy` with the result to free the memory. - pub fn create(self: NeverFailingAllocator, comptime T: type) *T { - return self.allocator.create(T) catch unreachable; - } - - /// `ptr` should be the return value of `create`, or otherwise - /// have the same address and alignment property. - pub fn destroy(self: NeverFailingAllocator, ptr: anytype) void { - self.allocator.destroy(ptr); - } - - /// Allocates an array of `n` items of type `T` and sets all the - /// items to `undefined`. Depending on the Allocator - /// implementation, it may be required to call `free` once the - /// memory is no longer needed, to avoid a resource leak. If the - /// `Allocator` implementation is unknown, then correct code will - /// call `free` when done. - /// - /// For allocating a single item, see `create`. - pub fn alloc(self: NeverFailingAllocator, comptime T: type, n: usize) []T { - return self.allocator.alloc(T, n) catch unreachable; - } - - pub fn allocWithOptions( - self: NeverFailingAllocator, - comptime Elem: type, - n: usize, - /// null means naturally aligned - comptime optional_alignment: ?u29, - comptime optional_sentinel: ?Elem, - ) AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) { - return self.allocator.allocWithOptions(Elem, n, optional_alignment, optional_sentinel) catch unreachable; - } - - pub fn allocWithOptionsRetAddr( - self: NeverFailingAllocator, - comptime Elem: type, - n: usize, - /// null means naturally aligned - comptime optional_alignment: ?u29, - comptime optional_sentinel: ?Elem, - return_address: usize, - ) AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) { - return self.allocator.allocWithOptionsRetAddr(Elem, n, optional_alignment, optional_sentinel, return_address) catch unreachable; - } - - fn AllocWithOptionsPayload(comptime Elem: type, comptime alignment: ?u29, comptime sentinel: ?Elem) type { - if(sentinel) |s| { - return [:s]align(alignment orelse @alignOf(Elem)) Elem; - } else { - return []align(alignment orelse @alignOf(Elem)) Elem; - } - } - - /// Allocates an array of `n + 1` items of type `T` and sets the first `n` - /// items to `undefined` and the last item to `sentinel`. Depending on the - /// Allocator implementation, it may be required to call `free` once the - /// memory is no longer needed, to avoid a resource leak. If the - /// `Allocator` implementation is unknown, then correct code will - /// call `free` when done. - /// - /// For allocating a single item, see `create`. - pub fn allocSentinel( - self: NeverFailingAllocator, - comptime Elem: type, - n: usize, - comptime sentinel: Elem, - ) [:sentinel]Elem { - return self.allocator.allocSentinel(Elem, n, sentinel) catch unreachable; - } - - pub fn alignedAlloc( - self: NeverFailingAllocator, - comptime T: type, - /// null means naturally aligned - comptime alignment: ?u29, - n: usize, - ) []align(alignment orelse @alignOf(T)) T { - return self.allocator.alignedAlloc(T, alignment, n) catch unreachable; - } - - pub inline fn allocAdvancedWithRetAddr( - self: NeverFailingAllocator, - comptime T: type, - /// null means naturally aligned - comptime alignment: ?u29, - n: usize, - return_address: usize, - ) []align(alignment orelse @alignOf(T)) T { - return self.allocator.allocAdvancedWithRetAddr(T, alignment, n, return_address) catch unreachable; - } - - fn allocWithSizeAndAlignment(self: NeverFailingAllocator, comptime size: usize, comptime alignment: u29, n: usize, return_address: usize) [*]align(alignment) u8 { - return self.allocator.allocWithSizeAndAlignment(alignment, size, alignment, n, return_address) catch unreachable; - } - - fn allocBytesWithAlignment(self: NeverFailingAllocator, comptime alignment: u29, byte_count: usize, return_address: usize) [*]align(alignment) u8 { - return self.allocator.allocBytesWithAlignment(alignment, byte_count, return_address) catch unreachable; - } - - /// Request to modify the size of an allocation. - /// - /// It is guaranteed to not move the pointer, however the allocator - /// implementation may refuse the resize request by returning `false`. - /// - /// `allocation` may be an empty slice, in which case a new allocation is made. - /// - /// `new_len` may be zero, in which case the allocation is freed. - pub fn resize(self: NeverFailingAllocator, allocation: anytype, new_len: usize) bool { - return self.allocator.resize(allocation, new_len); - } - - /// Request to modify the size of an allocation, allowing relocation. - /// - /// A non-`null` return value indicates the resize was successful. The - /// allocation may have same address, or may have been relocated. In either - /// case, the allocation now has size of `new_len`. A `null` return value - /// indicates that the resize would be equivalent to allocating new memory, - /// copying the bytes from the old memory, and then freeing the old memory. - /// In such case, it is more efficient for the caller to perform those - /// operations. - /// - /// `allocation` may be an empty slice, in which case a new allocation is made. - /// - /// `new_len` may be zero, in which case the allocation is freed. - pub fn remap(self: NeverFailingAllocator, allocation: anytype, new_len: usize) t: { - const Slice = @typeInfo(@TypeOf(allocation)).pointer; - break :t ?[]align(Slice.alignment) Slice.child; - } { - return self.allocator.remap(allocation, new_len); - } - - /// This function requests a new byte size for an existing allocation, which - /// can be larger, smaller, or the same size as the old memory allocation. - /// - /// If `new_n` is 0, this is the same as `free` and it always succeeds. - /// - /// `old_mem` may have length zero, which makes a new allocation. - /// - /// This function only fails on out-of-memory conditions, unlike: - /// * `remap` which returns `null` when the `Allocator` implementation cannot - /// do the realloc more efficiently than the caller - /// * `resize` which returns `false` when the `Allocator` implementation cannot - /// change the size without relocating the allocation. - pub fn realloc(self: NeverFailingAllocator, old_mem: anytype, new_n: usize) t: { - const Slice = @typeInfo(@TypeOf(old_mem)).pointer; - break :t []align(Slice.alignment) Slice.child; - } { - return self.allocator.realloc(old_mem, new_n) catch unreachable; - } - - pub fn reallocAdvanced( - self: NeverFailingAllocator, - old_mem: anytype, - new_n: usize, - return_address: usize, - ) t: { - const Slice = @typeInfo(@TypeOf(old_mem)).pointer; - break :t []align(Slice.alignment) Slice.child; - } { - return self.allocator.reallocAdvanced(old_mem, new_n, return_address) catch unreachable; - } - - /// Free an array allocated with `alloc`. - /// If memory has length 0, free is a no-op. - /// To free a single item, see `destroy`. - pub fn free(self: NeverFailingAllocator, memory: anytype) void { - self.allocator.free(memory); - } - - /// Copies `m` to newly allocated memory. Caller owns the memory. - pub fn dupe(self: NeverFailingAllocator, comptime T: type, m: []const T) []T { - return self.allocator.dupe(T, m) catch unreachable; - } - - /// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory. - pub fn dupeZ(self: NeverFailingAllocator, comptime T: type, m: []const T) [:0]T { - return self.allocator.dupeZ(T, m) catch unreachable; - } -}; - -pub const NeverFailingArenaAllocator = struct { // MARK: NeverFailingArena - arena: std.heap.ArenaAllocator, - - pub fn init(child_allocator: NeverFailingAllocator) NeverFailingArenaAllocator { - return .{ - .arena = .init(child_allocator.allocator), - }; - } - - pub fn deinit(self: NeverFailingArenaAllocator) void { - self.arena.deinit(); - } - - pub fn allocator(self: *NeverFailingArenaAllocator) NeverFailingAllocator { - return .{ - .allocator = self.arena.allocator(), - .IAssertThatTheProvidedAllocatorCantFail = {}, - }; - } - - /// Resets the arena allocator and frees all allocated memory. - /// - /// `mode` defines how the currently allocated memory is handled. - /// See the variant documentation for `ResetMode` for the effects of each mode. - /// - /// The function will return whether the reset operation was successful or not. - /// If the reallocation failed `false` is returned. The arena will still be fully - /// functional in that case, all memory is released. Future allocations just might - /// be slower. - /// - /// NOTE: If `mode` is `free_all`, the function will always return `true`. - pub fn reset(self: *NeverFailingArenaAllocator, mode: std.heap.ArenaAllocator.ResetMode) bool { - return self.arena.reset(mode); - } - - pub fn shrinkAndFree(self: *NeverFailingArenaAllocator) void { - const node = self.arena.state.buffer_list.first orelse return; - const allocBuf = @as([*]u8, @ptrCast(node))[0..node.data]; - const dataSize = std.mem.alignForward(usize, @sizeOf(std.SinglyLinkedList(usize).Node) + self.arena.state.end_index, @alignOf(std.SinglyLinkedList(usize).Node)); - if(self.arena.child_allocator.rawResize(allocBuf, @enumFromInt(std.math.log2(@alignOf(std.SinglyLinkedList(usize).Node))), dataSize, @returnAddress())) { - node.data = dataSize; - } - } -}; - -pub const BufferFallbackAllocator = struct { // MARK: BufferFallbackAllocator - fixedBuffer: std.heap.FixedBufferAllocator, - fallbackAllocator: NeverFailingAllocator, - - pub fn init(buffer: []u8, fallbackAllocator: NeverFailingAllocator) BufferFallbackAllocator { - return .{ - .fixedBuffer = .init(buffer), - .fallbackAllocator = fallbackAllocator, - }; - } - - pub fn allocator(self: *BufferFallbackAllocator) NeverFailingAllocator { - return .{ - .allocator = .{ - .vtable = &.{ - .alloc = &alloc, - .resize = &resize, - .free = &free, - }, - .ptr = self, - }, - .IAssertThatTheProvidedAllocatorCantFail = {}, - }; - } - - fn alloc(ctx: *anyopaque, len: usize, log2_ptr_align: u8, ra: usize) ?[*]u8 { - const self: *BufferFallbackAllocator = @ptrCast(@alignCast(ctx)); - return self.fixedBuffer.allocator().rawAlloc(len, log2_ptr_align, ra) orelse - return self.fallbackAllocator.rawAlloc(len, log2_ptr_align, ra); - } - - fn resize(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, new_len: usize, ra: usize) bool { - const self: *BufferFallbackAllocator = @ptrCast(@alignCast(ctx)); - if(self.fixedBuffer.ownsPtr(buf.ptr)) { - return self.fixedBuffer.allocator().rawResize(buf, log2_buf_align, new_len, ra); - } else { - return self.fallbackAllocator.rawResize(buf, log2_buf_align, new_len, ra); - } - } - - fn free(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, ra: usize) void { - const self: *BufferFallbackAllocator = @ptrCast(@alignCast(ctx)); - if(self.fixedBuffer.ownsPtr(buf.ptr)) { - return self.fixedBuffer.allocator().rawFree(buf, log2_buf_align, ra); - } else { - return self.fallbackAllocator.rawFree(buf, log2_buf_align, ra); - } - } -}; - /// A simple binary heap. /// Thread safe and blocking. /// Expects T to have a `biggerThan(T) bool` function @@ -1242,7 +707,7 @@ pub const ThreadPool = struct { // MARK: ThreadPool fn run(self: *ThreadPool, id: usize) void { // In case any of the tasks wants to allocate memory: - var sta = StackAllocator.init(main.globalAllocator, 1 << 23); + var sta = main.heap.StackAllocator.init(main.globalAllocator, 1 << 23); defer sta.deinit(); main.stackAllocator = sta.allocator(); @@ -1321,7 +786,7 @@ pub fn DynamicPackedIntArray(size: comptime_int) type { // MARK: DynamicPackedIn const Self = @This(); - pub fn initCapacity(allocator: main.utils.NeverFailingAllocator, bitSize: u5) Self { + pub fn initCapacity(allocator: main.heap.NeverFailingAllocator, bitSize: u5) Self { std.debug.assert(bitSize == 0 or bitSize & bitSize - 1 == 0); // Must be a power of 2 return .{ .data = allocator.alignedAlloc(u32, 64, @as(usize, @divExact(size, @bitSizeOf(u32)))*bitSize), @@ -1329,7 +794,7 @@ pub fn DynamicPackedIntArray(size: comptime_int) type { // MARK: DynamicPackedIn }; } - pub fn deinit(self: *Self, allocator: main.utils.NeverFailingAllocator) void { + pub fn deinit(self: *Self, allocator: main.heap.NeverFailingAllocator) void { allocator.free(self.data); self.* = .{}; } @@ -1343,7 +808,7 @@ pub fn DynamicPackedIntArray(size: comptime_int) type { // MARK: DynamicPackedIn return result; } - pub fn resizeOnce(self: *Self, allocator: main.utils.NeverFailingAllocator) void { + pub fn resizeOnce(self: *Self, allocator: main.heap.NeverFailingAllocator) void { const newBitSize = if(self.bitSize != 0) self.bitSize*2 else 1; var newSelf = Self.initCapacity(allocator, newBitSize); diff --git a/src/utils/heap.zig b/src/utils/heap.zig new file mode 100644 index 00000000..54b8d784 --- /dev/null +++ b/src/utils/heap.zig @@ -0,0 +1,490 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const main = @import("root"); + +/// Allows for stack-like allocations in a fast and safe way. +/// It is safe in the sense that a regular allocator will be used when the buffer is full. +pub const StackAllocator = struct { // MARK: StackAllocator + const AllocationTrailer = packed struct {wasFreed: bool, previousAllocationTrailer: u31}; + backingAllocator: NeverFailingAllocator, + buffer: []align(4096) u8, + index: usize, + + pub fn init(backingAllocator: NeverFailingAllocator, size: u31) StackAllocator { + return .{ + .backingAllocator = backingAllocator, + .buffer = backingAllocator.alignedAlloc(u8, 4096, size), + .index = 0, + }; + } + + pub fn deinit(self: StackAllocator) void { + if(self.index != 0) { + std.log.err("Memory leak in Stack Allocator", .{}); + } + self.backingAllocator.free(self.buffer); + } + + pub fn allocator(self: *StackAllocator) NeverFailingAllocator { + return .{ + .allocator = .{ + .vtable = &.{ + .alloc = &alloc, + .resize = &resize, + .remap = &remap, + .free = &free, + }, + .ptr = self, + }, + .IAssertThatTheProvidedAllocatorCantFail = {}, + }; + } + + fn isInsideBuffer(self: *StackAllocator, buf: []u8) bool { + const bufferStart = @intFromPtr(self.buffer.ptr); + const bufferEnd = bufferStart + self.buffer.len; + const compare = @intFromPtr(buf.ptr); + return compare >= bufferStart and compare < bufferEnd; + } + + fn indexInBuffer(self: *StackAllocator, buf: []u8) usize { + const bufferStart = @intFromPtr(self.buffer.ptr); + const compare = @intFromPtr(buf.ptr); + return compare - bufferStart; + } + + fn getTrueAllocationEnd(start: usize, len: usize) usize { + const trailerStart = std.mem.alignForward(usize, start + len, @alignOf(AllocationTrailer)); + return trailerStart + @sizeOf(AllocationTrailer); + } + + fn getTrailerBefore(self: *StackAllocator, end: usize) *AllocationTrailer { + const trailerStart = end - @sizeOf(AllocationTrailer); + return @ptrCast(@alignCast(self.buffer[trailerStart..].ptr)); + } + + fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: usize) ?[*]u8 { + const self: *StackAllocator = @ptrCast(@alignCast(ctx)); + const start = std.mem.alignForward(usize, self.index, @as(usize, 1) << @intCast(@intFromEnum(alignment))); + const end = getTrueAllocationEnd(start, len); + if(end >= self.buffer.len) return self.backingAllocator.rawAlloc(len, alignment, ret_addr); + const trailer = self.getTrailerBefore(end); + trailer.* = .{.wasFreed = false, .previousAllocationTrailer = @intCast(self.index)}; + self.index = end; + return self.buffer.ptr + start; + } + + fn resize(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) bool { + const self: *StackAllocator = @ptrCast(@alignCast(ctx)); + if(self.isInsideBuffer(memory)) { + const start = self.indexInBuffer(memory); + const end = getTrueAllocationEnd(start, memory.len); + if(end != self.index) return false; + const newEnd = getTrueAllocationEnd(start, new_len); + if(newEnd >= self.buffer.len) return false; + + const trailer = self.getTrailerBefore(end); + std.debug.assert(!trailer.wasFreed); + const newTrailer = self.getTrailerBefore(newEnd); + + newTrailer.* = .{.wasFreed = false, .previousAllocationTrailer = trailer.previousAllocationTrailer}; + self.index = newEnd; + return true; + } else { + return self.backingAllocator.rawResize(memory, alignment, new_len, ret_addr); + } + } + + fn remap(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 { + if(resize(ctx, memory, alignment, new_len, ret_addr)) return memory.ptr; + return null; + } + + fn free(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, ret_addr: usize) void { + const self: *StackAllocator = @ptrCast(@alignCast(ctx)); + if(self.isInsideBuffer(memory)) { + const start = self.indexInBuffer(memory); + const end = getTrueAllocationEnd(start, memory.len); + const trailer = self.getTrailerBefore(end); + std.debug.assert(!trailer.wasFreed); // Double Free + + if(end == self.index) { + self.index = trailer.previousAllocationTrailer; + if(self.index != 0) { + var previousTrailer = self.getTrailerBefore(trailer.previousAllocationTrailer); + while(previousTrailer.wasFreed) { + self.index = previousTrailer.previousAllocationTrailer; + if(self.index == 0) break; + previousTrailer = self.getTrailerBefore(previousTrailer.previousAllocationTrailer); + } + } + } else { + trailer.wasFreed = true; + } + } else { + self.backingAllocator.rawFree(memory, alignment, ret_addr); + } + } +}; + +/// An allocator that handles OutOfMemory situations by panicing or freeing memory(TODO), making it safe to ignore errors. +pub const ErrorHandlingAllocator = struct { // MARK: ErrorHandlingAllocator + backingAllocator: Allocator, + + pub fn init(backingAllocator: Allocator) ErrorHandlingAllocator { + return .{ + .backingAllocator = backingAllocator, + }; + } + + pub fn allocator(self: *ErrorHandlingAllocator) NeverFailingAllocator { + return .{ + .allocator = .{ + .vtable = &.{ + .alloc = &alloc, + .resize = &resize, + .remap = &remap, + .free = &free, + }, + .ptr = self, + }, + .IAssertThatTheProvidedAllocatorCantFail = {}, + }; + } + + fn handleError() noreturn { + @panic("Out Of Memory. Please download more RAM, reduce the render distance, or close some of your 100 browser tabs."); + } + + /// Return a pointer to `len` bytes with specified `alignment`, or return + /// `null` indicating the allocation failed. + /// + /// `ret_addr` is optionally provided as the first return address of the + /// allocation call stack. If the value is `0` it means no return address + /// has been provided. + fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: usize) ?[*]u8 { + const self: *ErrorHandlingAllocator = @ptrCast(@alignCast(ctx)); + return self.backingAllocator.rawAlloc(len, alignment, ret_addr) orelse handleError(); + } + + /// Attempt to expand or shrink memory in place. + /// + /// `memory.len` must equal the length requested from the most recent + /// successful call to `alloc`, `resize`, or `remap`. `alignment` must + /// equal the same value that was passed as the `alignment` parameter to + /// the original `alloc` call. + /// + /// A result of `true` indicates the resize was successful and the + /// allocation now has the same address but a size of `new_len`. `false` + /// indicates the resize could not be completed without moving the + /// allocation to a different address. + /// + /// `new_len` must be greater than zero. + /// + /// `ret_addr` is optionally provided as the first return address of the + /// allocation call stack. If the value is `0` it means no return address + /// has been provided. + fn resize(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) bool { + const self: *ErrorHandlingAllocator = @ptrCast(@alignCast(ctx)); + return self.backingAllocator.rawResize(memory, alignment, new_len, ret_addr); + } + + /// Attempt to expand or shrink memory, allowing relocation. + /// + /// `memory.len` must equal the length requested from the most recent + /// successful call to `alloc`, `resize`, or `remap`. `alignment` must + /// equal the same value that was passed as the `alignment` parameter to + /// the original `alloc` call. + /// + /// A non-`null` return value indicates the resize was successful. The + /// allocation may have same address, or may have been relocated. In either + /// case, the allocation now has size of `new_len`. A `null` return value + /// indicates that the resize would be equivalent to allocating new memory, + /// copying the bytes from the old memory, and then freeing the old memory. + /// In such case, it is more efficient for the caller to perform the copy. + /// + /// `new_len` must be greater than zero. + /// + /// `ret_addr` is optionally provided as the first return address of the + /// allocation call stack. If the value is `0` it means no return address + /// has been provided. + fn remap(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 { + const self: *ErrorHandlingAllocator = @ptrCast(@alignCast(ctx)); + return self.backingAllocator.rawRemap(memory, alignment, new_len, ret_addr); + } + + /// Free and invalidate a region of memory. + /// + /// `memory.len` must equal the length requested from the most recent + /// successful call to `alloc`, `resize`, or `remap`. `alignment` must + /// equal the same value that was passed as the `alignment` parameter to + /// the original `alloc` call. + /// + /// `ret_addr` is optionally provided as the first return address of the + /// allocation call stack. If the value is `0` it means no return address + /// has been provided. + fn free(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, ret_addr: usize) void { + const self: *ErrorHandlingAllocator = @ptrCast(@alignCast(ctx)); + self.backingAllocator.rawFree(memory, alignment, ret_addr); + } +}; + +/// An allocator interface signaling that you can use +pub const NeverFailingAllocator = struct { // MARK: NeverFailingAllocator + allocator: Allocator, + IAssertThatTheProvidedAllocatorCantFail: void, + + const Alignment = std.mem.Alignment; + const math = std.math; + + /// This function is not intended to be called except from within the + /// implementation of an `Allocator`. + pub inline fn rawAlloc(a: NeverFailingAllocator, len: usize, alignment: Alignment, ret_addr: usize) ?[*]u8 { + return a.allocator.vtable.alloc(a.allocator.ptr, len, alignment, ret_addr); + } + + /// This function is not intended to be called except from within the + /// implementation of an `Allocator`. + pub inline fn rawResize(a: NeverFailingAllocator, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) bool { + return a.allocator.vtable.resize(a.allocator.ptr, memory, alignment, new_len, ret_addr); + } + + /// This function is not intended to be called except from within the + /// implementation of an `Allocator`. + pub inline fn rawRemap(a: NeverFailingAllocator, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) ?[*]u8 { + return a.allocator.vtable.remap(a.allocator.ptr, memory, alignment, new_len, ret_addr); + } + + /// This function is not intended to be called except from within the + /// implementation of an `Allocator`. + pub inline fn rawFree(a: NeverFailingAllocator, memory: []u8, alignment: Alignment, ret_addr: usize) void { + return a.allocator.vtable.free(a.allocator.ptr, memory, alignment, ret_addr); + } + + /// Returns a pointer to undefined memory. + /// Call `destroy` with the result to free the memory. + pub fn create(self: NeverFailingAllocator, comptime T: type) *T { + return self.allocator.create(T) catch unreachable; + } + + /// `ptr` should be the return value of `create`, or otherwise + /// have the same address and alignment property. + pub fn destroy(self: NeverFailingAllocator, ptr: anytype) void { + self.allocator.destroy(ptr); + } + + /// Allocates an array of `n` items of type `T` and sets all the + /// items to `undefined`. Depending on the Allocator + /// implementation, it may be required to call `free` once the + /// memory is no longer needed, to avoid a resource leak. If the + /// `Allocator` implementation is unknown, then correct code will + /// call `free` when done. + /// + /// For allocating a single item, see `create`. + pub fn alloc(self: NeverFailingAllocator, comptime T: type, n: usize) []T { + return self.allocator.alloc(T, n) catch unreachable; + } + + pub fn allocWithOptions( + self: NeverFailingAllocator, + comptime Elem: type, + n: usize, + /// null means naturally aligned + comptime optional_alignment: ?u29, + comptime optional_sentinel: ?Elem, + ) AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) { + return self.allocator.allocWithOptions(Elem, n, optional_alignment, optional_sentinel) catch unreachable; + } + + pub fn allocWithOptionsRetAddr( + self: NeverFailingAllocator, + comptime Elem: type, + n: usize, + /// null means naturally aligned + comptime optional_alignment: ?u29, + comptime optional_sentinel: ?Elem, + return_address: usize, + ) AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) { + return self.allocator.allocWithOptionsRetAddr(Elem, n, optional_alignment, optional_sentinel, return_address) catch unreachable; + } + + fn AllocWithOptionsPayload(comptime Elem: type, comptime alignment: ?u29, comptime sentinel: ?Elem) type { + if(sentinel) |s| { + return [:s]align(alignment orelse @alignOf(Elem)) Elem; + } else { + return []align(alignment orelse @alignOf(Elem)) Elem; + } + } + + /// Allocates an array of `n + 1` items of type `T` and sets the first `n` + /// items to `undefined` and the last item to `sentinel`. Depending on the + /// Allocator implementation, it may be required to call `free` once the + /// memory is no longer needed, to avoid a resource leak. If the + /// `Allocator` implementation is unknown, then correct code will + /// call `free` when done. + /// + /// For allocating a single item, see `create`. + pub fn allocSentinel( + self: NeverFailingAllocator, + comptime Elem: type, + n: usize, + comptime sentinel: Elem, + ) [:sentinel]Elem { + return self.allocator.allocSentinel(Elem, n, sentinel) catch unreachable; + } + + pub fn alignedAlloc( + self: NeverFailingAllocator, + comptime T: type, + /// null means naturally aligned + comptime alignment: ?u29, + n: usize, + ) []align(alignment orelse @alignOf(T)) T { + return self.allocator.alignedAlloc(T, alignment, n) catch unreachable; + } + + pub inline fn allocAdvancedWithRetAddr( + self: NeverFailingAllocator, + comptime T: type, + /// null means naturally aligned + comptime alignment: ?u29, + n: usize, + return_address: usize, + ) []align(alignment orelse @alignOf(T)) T { + return self.allocator.allocAdvancedWithRetAddr(T, alignment, n, return_address) catch unreachable; + } + + fn allocWithSizeAndAlignment(self: NeverFailingAllocator, comptime size: usize, comptime alignment: u29, n: usize, return_address: usize) [*]align(alignment) u8 { + return self.allocator.allocWithSizeAndAlignment(alignment, size, alignment, n, return_address) catch unreachable; + } + + fn allocBytesWithAlignment(self: NeverFailingAllocator, comptime alignment: u29, byte_count: usize, return_address: usize) [*]align(alignment) u8 { + return self.allocator.allocBytesWithAlignment(alignment, byte_count, return_address) catch unreachable; + } + + /// Request to modify the size of an allocation. + /// + /// It is guaranteed to not move the pointer, however the allocator + /// implementation may refuse the resize request by returning `false`. + /// + /// `allocation` may be an empty slice, in which case a new allocation is made. + /// + /// `new_len` may be zero, in which case the allocation is freed. + pub fn resize(self: NeverFailingAllocator, allocation: anytype, new_len: usize) bool { + return self.allocator.resize(allocation, new_len); + } + + /// Request to modify the size of an allocation, allowing relocation. + /// + /// A non-`null` return value indicates the resize was successful. The + /// allocation may have same address, or may have been relocated. In either + /// case, the allocation now has size of `new_len`. A `null` return value + /// indicates that the resize would be equivalent to allocating new memory, + /// copying the bytes from the old memory, and then freeing the old memory. + /// In such case, it is more efficient for the caller to perform those + /// operations. + /// + /// `allocation` may be an empty slice, in which case a new allocation is made. + /// + /// `new_len` may be zero, in which case the allocation is freed. + pub fn remap(self: NeverFailingAllocator, allocation: anytype, new_len: usize) t: { + const Slice = @typeInfo(@TypeOf(allocation)).pointer; + break :t ?[]align(Slice.alignment) Slice.child; + } { + return self.allocator.remap(allocation, new_len); + } + + /// This function requests a new byte size for an existing allocation, which + /// can be larger, smaller, or the same size as the old memory allocation. + /// + /// If `new_n` is 0, this is the same as `free` and it always succeeds. + /// + /// `old_mem` may have length zero, which makes a new allocation. + /// + /// This function only fails on out-of-memory conditions, unlike: + /// * `remap` which returns `null` when the `Allocator` implementation cannot + /// do the realloc more efficiently than the caller + /// * `resize` which returns `false` when the `Allocator` implementation cannot + /// change the size without relocating the allocation. + pub fn realloc(self: NeverFailingAllocator, old_mem: anytype, new_n: usize) t: { + const Slice = @typeInfo(@TypeOf(old_mem)).pointer; + break :t []align(Slice.alignment) Slice.child; + } { + return self.allocator.realloc(old_mem, new_n) catch unreachable; + } + + pub fn reallocAdvanced( + self: NeverFailingAllocator, + old_mem: anytype, + new_n: usize, + return_address: usize, + ) t: { + const Slice = @typeInfo(@TypeOf(old_mem)).pointer; + break :t []align(Slice.alignment) Slice.child; + } { + return self.allocator.reallocAdvanced(old_mem, new_n, return_address) catch unreachable; + } + + /// Free an array allocated with `alloc`. + /// If memory has length 0, free is a no-op. + /// To free a single item, see `destroy`. + pub fn free(self: NeverFailingAllocator, memory: anytype) void { + self.allocator.free(memory); + } + + /// Copies `m` to newly allocated memory. Caller owns the memory. + pub fn dupe(self: NeverFailingAllocator, comptime T: type, m: []const T) []T { + return self.allocator.dupe(T, m) catch unreachable; + } + + /// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory. + pub fn dupeZ(self: NeverFailingAllocator, comptime T: type, m: []const T) [:0]T { + return self.allocator.dupeZ(T, m) catch unreachable; + } +}; + +pub const NeverFailingArenaAllocator = struct { // MARK: NeverFailingArena + arena: std.heap.ArenaAllocator, + + pub fn init(child_allocator: NeverFailingAllocator) NeverFailingArenaAllocator { + return .{ + .arena = .init(child_allocator.allocator), + }; + } + + pub fn deinit(self: NeverFailingArenaAllocator) void { + self.arena.deinit(); + } + + pub fn allocator(self: *NeverFailingArenaAllocator) NeverFailingAllocator { + return .{ + .allocator = self.arena.allocator(), + .IAssertThatTheProvidedAllocatorCantFail = {}, + }; + } + + /// Resets the arena allocator and frees all allocated memory. + /// + /// `mode` defines how the currently allocated memory is handled. + /// See the variant documentation for `ResetMode` for the effects of each mode. + /// + /// The function will return whether the reset operation was successful or not. + /// If the reallocation failed `false` is returned. The arena will still be fully + /// functional in that case, all memory is released. Future allocations just might + /// be slower. + /// + /// NOTE: If `mode` is `free_all`, the function will always return `true`. + pub fn reset(self: *NeverFailingArenaAllocator, mode: std.heap.ArenaAllocator.ResetMode) bool { + return self.arena.reset(mode); + } + + pub fn shrinkAndFree(self: *NeverFailingArenaAllocator) void { + const node = self.arena.state.buffer_list.first orelse return; + const allocBuf = @as([*]u8, @ptrCast(node))[0..node.data]; + const dataSize = std.mem.alignForward(usize, @sizeOf(std.SinglyLinkedList(usize).Node) + self.arena.state.end_index, @alignOf(std.SinglyLinkedList(usize).Node)); + if(self.arena.child_allocator.rawResize(allocBuf, @enumFromInt(std.math.log2(@alignOf(std.SinglyLinkedList(usize).Node))), dataSize, @returnAddress())) { + node.data = dataSize; + } + } +}; diff --git a/src/utils/list.zig b/src/utils/list.zig index 67585464..1a5ca40b 100644 --- a/src/utils/list.zig +++ b/src/utils/list.zig @@ -2,7 +2,7 @@ const builtin = @import("builtin"); const std = @import("std"); const main = @import("../main.zig"); -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; fn growCapacity(current: usize, minimum: usize) usize { var new = current; diff --git a/src/zon.zig b/src/zon.zig index 7d0d9b44..1bcffb14 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -1,7 +1,7 @@ const std = @import("std"); const main = @import("main.zig"); -const NeverFailingAllocator = main.utils.NeverFailingAllocator; +const NeverFailingAllocator = main.heap.NeverFailingAllocator; const List = main.List; pub const ZonElement = union(enum) { // MARK: Zon @@ -233,7 +233,7 @@ pub const ZonElement = union(enum) { // MARK: Zon }, .vector => { const len = @typeInfo(@TypeOf(value)).vector.len; - const result = initArray(main.utils.NeverFailingAllocator{.allocator = allocator, .IAssertThatTheProvidedAllocatorCantFail = {}}); + const result = initArray(main.heap.NeverFailingAllocator{.allocator = allocator, .IAssertThatTheProvidedAllocatorCantFail = {}}); result.array.ensureCapacity(len); inline for(0..len) |i| { result.array.appendAssumeCapacity(createElementFromRandomType(value[i], allocator));