udpate to Zig master
This commit is contained in:
parent
f2a302b7a4
commit
bdb4b0537d
@ -1,6 +1,8 @@
|
|||||||
// ecs
|
// ecs
|
||||||
pub const EntityTraitsType = @import("ecs/entity.zig").EntityTraitsType;
|
pub const EntityTraitsType = @import("ecs/entity.zig").EntityTraitsType;
|
||||||
|
|
||||||
|
pub const ComponentStorage = @import("ecs/component_storage.zig").ComponentStorage;
|
||||||
|
|
||||||
pub const Entity = @import("ecs/registry.zig").Entity;
|
pub const Entity = @import("ecs/registry.zig").Entity;
|
||||||
pub const Registry = @import("ecs/registry.zig").Registry;
|
pub const Registry = @import("ecs/registry.zig").Registry;
|
||||||
pub const BasicView = @import("ecs/views.zig").BasicView;
|
pub const BasicView = @import("ecs/views.zig").BasicView;
|
||||||
|
@ -17,9 +17,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type {
|
|||||||
// non-zero sized type. That will make is_empty_struct false in deinit always so we can't use it. Instead, we stick
|
// non-zero sized type. That will make is_empty_struct false in deinit always so we can't use it. Instead, we stick
|
||||||
// a small dummy struct in the instances ArrayList so it can safely be deallocated.
|
// a small dummy struct in the instances ArrayList so it can safely be deallocated.
|
||||||
// Perhaps we should just allocate instances with a dummy allocator or the tmp allocator?
|
// Perhaps we should just allocate instances with a dummy allocator or the tmp allocator?
|
||||||
comptime var CompOrAlmostEmptyT = CompT;
|
comptime var CompOrAlmostEmptyT = if (is_empty_struct) struct { dummy: u1 } else CompT;
|
||||||
if (is_empty_struct)
|
|
||||||
CompOrAlmostEmptyT = struct { dummy: u1 };
|
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
@ -27,7 +25,8 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type {
|
|||||||
set: *SparseSet(EntityT),
|
set: *SparseSet(EntityT),
|
||||||
instances: std.ArrayList(CompOrAlmostEmptyT),
|
instances: std.ArrayList(CompOrAlmostEmptyT),
|
||||||
allocator: ?*std.mem.Allocator,
|
allocator: ?*std.mem.Allocator,
|
||||||
super: usize = 0, /// doesnt really belong here...used to denote group ownership
|
/// doesnt really belong here...used to denote group ownership
|
||||||
|
super: usize = 0,
|
||||||
safe_deinit: fn (*Self) void,
|
safe_deinit: fn (*Self) void,
|
||||||
safe_swap: fn (*Self, EntityT, EntityT) void,
|
safe_swap: fn (*Self, EntityT, EntityT) void,
|
||||||
construction: Signal(EntityT),
|
construction: Signal(EntityT),
|
||||||
@ -40,14 +39,16 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type {
|
|||||||
.instances = undefined,
|
.instances = undefined,
|
||||||
.safe_deinit = struct {
|
.safe_deinit = struct {
|
||||||
fn deinit(self: *Self) void {
|
fn deinit(self: *Self) void {
|
||||||
if (!is_empty_struct)
|
if (!is_empty_struct) {
|
||||||
self.instances.deinit();
|
self.instances.deinit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.deinit,
|
}.deinit,
|
||||||
.safe_swap = struct {
|
.safe_swap = struct {
|
||||||
fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void {
|
fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void {
|
||||||
if (!is_empty_struct)
|
if (!is_empty_struct) {
|
||||||
std.mem.swap(CompT, &self.instances.items[self.set.index(lhs)], &self.instances.items[self.set.index(rhs)]);
|
std.mem.swap(CompT, &self.instances.items[self.set.index(lhs)], &self.instances.items[self.set.index(rhs)]);
|
||||||
|
}
|
||||||
self.set.swap(lhs, rhs);
|
self.set.swap(lhs, rhs);
|
||||||
}
|
}
|
||||||
}.swap,
|
}.swap,
|
||||||
@ -57,8 +58,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type {
|
|||||||
.destruction = Signal(EntityT).init(allocator),
|
.destruction = Signal(EntityT).init(allocator),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!is_empty_struct)
|
if (!is_empty_struct) {
|
||||||
store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator);
|
store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
@ -66,8 +68,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type {
|
|||||||
pub fn initPtr(allocator: *std.mem.Allocator) *Self {
|
pub fn initPtr(allocator: *std.mem.Allocator) *Self {
|
||||||
var store = allocator.create(Self) catch unreachable;
|
var store = allocator.create(Self) catch unreachable;
|
||||||
store.set = SparseSet(EntityT).initPtr(allocator);
|
store.set = SparseSet(EntityT).initPtr(allocator);
|
||||||
if (!is_empty_struct)
|
if (!is_empty_struct) {
|
||||||
store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator);
|
store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator);
|
||||||
|
}
|
||||||
store.allocator = allocator;
|
store.allocator = allocator;
|
||||||
store.super = 0;
|
store.super = 0;
|
||||||
store.construction = Signal(EntityT).init(allocator);
|
store.construction = Signal(EntityT).init(allocator);
|
||||||
@ -77,8 +80,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type {
|
|||||||
// since we are stored as a pointer, we need to catpure this
|
// since we are stored as a pointer, we need to catpure this
|
||||||
store.safe_deinit = struct {
|
store.safe_deinit = struct {
|
||||||
fn deinit(self: *Self) void {
|
fn deinit(self: *Self) void {
|
||||||
if (!is_empty_struct)
|
if (!is_empty_struct) {
|
||||||
self.instances.deinit();
|
self.instances.deinit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.deinit;
|
}.deinit;
|
||||||
|
|
||||||
@ -157,7 +161,12 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub usingnamespace if (is_empty_struct)
|
pub usingnamespace if (is_empty_struct)
|
||||||
struct {}
|
struct {
|
||||||
|
/// Sort Entities according to the given comparison function
|
||||||
|
pub fn sort(self: Self, comptime sortFn: fn (void, EntityT, EntityT) bool) void {
|
||||||
|
self.set.sort(sortFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
struct {
|
struct {
|
||||||
/// Direct access to the array of objects
|
/// Direct access to the array of objects
|
||||||
@ -189,6 +198,21 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type {
|
|||||||
pub fn tryGetConst(self: *Self, entity: EntityT) ?CompT {
|
pub fn tryGetConst(self: *Self, entity: EntityT) ?CompT {
|
||||||
return if (self.set.contains(entity)) self.instances.items[self.set.index(entity)] else null;
|
return if (self.set.contains(entity)) self.instances.items[self.set.index(entity)] else null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sort Entities or Components according to the given comparison function
|
||||||
|
pub fn sort(self: Self, comptime T: type, comptime sortFn: fn (void, T, T) bool) void {
|
||||||
|
std.debug.assert(T == EntityT or T == CompT);
|
||||||
|
if (T == EntityT) {
|
||||||
|
self.set.sortSub(sortFn, CompT, self.instances.items);
|
||||||
|
} else if (T == CompT) {
|
||||||
|
// essentially need to be able to call a sort method with a bound fn. That fn would then use sortFn along
|
||||||
|
// with self.instances.
|
||||||
|
// fn sorter(self: Self, a: T, b: T, sortFn) bool {
|
||||||
|
// return sortFn(self.instances[a], self.instances[b]);
|
||||||
|
// }
|
||||||
|
//return compare(std::as_const(instances[underlying_type::index(lhs)]), std::as_const(instances[underlying_type::index(rhs)]));
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Direct access to the array of entities
|
/// Direct access to the array of entities
|
||||||
@ -207,8 +231,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(self: *Self) void {
|
pub fn clear(self: *Self) void {
|
||||||
if (!is_empty_struct)
|
if (!is_empty_struct) {
|
||||||
self.instances.items.len = 0;
|
self.instances.items.len = 0;
|
||||||
|
}
|
||||||
self.set.clear();
|
self.set.clear();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -251,12 +276,15 @@ test "iterate" {
|
|||||||
store.add(7, 66.45);
|
store.add(7, 66.45);
|
||||||
|
|
||||||
for (store.data()) |entity, i| {
|
for (store.data()) |entity, i| {
|
||||||
if (i == 0)
|
if (i == 0) {
|
||||||
std.testing.expectEqual(entity, 3);
|
std.testing.expectEqual(entity, 3);
|
||||||
if (i == 1)
|
}
|
||||||
|
if (i == 1) {
|
||||||
std.testing.expectEqual(entity, 5);
|
std.testing.expectEqual(entity, 5);
|
||||||
if (i == 2)
|
}
|
||||||
|
if (i == 2) {
|
||||||
std.testing.expectEqual(entity, 7);
|
std.testing.expectEqual(entity, 7);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,3 +328,54 @@ test "signals" {
|
|||||||
store.replace(4, 45.64);
|
store.replace(4, 45.64);
|
||||||
store.remove(4);
|
store.remove(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const asc_u32 = std.sort.asc(u32);
|
||||||
|
const desc_u32 = std.sort.desc(u32);
|
||||||
|
|
||||||
|
test "sort empty component" {
|
||||||
|
const Empty = struct {};
|
||||||
|
|
||||||
|
var store = ComponentStorage(Empty, u32).initPtr(std.testing.allocator);
|
||||||
|
defer store.deinit();
|
||||||
|
|
||||||
|
store.add(1, Empty{});
|
||||||
|
store.add(2, Empty{});
|
||||||
|
store.add(0, Empty{});
|
||||||
|
|
||||||
|
store.sort(asc_u32);
|
||||||
|
for (store.data()) |e, i| {
|
||||||
|
std.testing.expectEqual(@intCast(u32, i), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
store.sort(desc_u32);
|
||||||
|
var counter: u32 = 2;
|
||||||
|
for (store.data()) |e, i| {
|
||||||
|
std.testing.expectEqual(counter, e);
|
||||||
|
if (counter > 0) counter -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const asc_f32 = std.sort.asc(f32);
|
||||||
|
const desc_f32 = std.sort.desc(f32);
|
||||||
|
|
||||||
|
test "sort component" {
|
||||||
|
std.debug.warn("\n", .{});
|
||||||
|
|
||||||
|
var store = ComponentStorage(f32, u32).initPtr(std.testing.allocator);
|
||||||
|
defer store.deinit();
|
||||||
|
|
||||||
|
store.add(1, @as(f32, 1.1));
|
||||||
|
store.add(2, @as(f32, 2.2));
|
||||||
|
store.add(0, @as(f32, 0.0));
|
||||||
|
|
||||||
|
store.sort(f32, asc_f32);
|
||||||
|
for (store.raw()) |e, i| {
|
||||||
|
// std.debug.warn("{}: {}\n", .{i, e});
|
||||||
|
// std.testing.expectEqual(@intCast(u32, i), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
store.sort(f32, desc_f32);
|
||||||
|
for (store.raw()) |e, i| {
|
||||||
|
// std.testing.expectEqual(counter, e);
|
||||||
|
}
|
||||||
|
}
|
@ -78,7 +78,7 @@ pub const Registry = struct {
|
|||||||
allocator.destroy(self);
|
allocator.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybeValidIf(self: *GroupData, entity: Entity) void {
|
pub fn maybeValidIf(self: *GroupData, entity: Entity) void {
|
||||||
const isValid: bool = blk: {
|
const isValid: bool = blk: {
|
||||||
for (self.owned) |tid| {
|
for (self.owned) |tid| {
|
||||||
const ptr = self.registry.components.getValue(tid).?;
|
const ptr = self.registry.components.getValue(tid).?;
|
||||||
@ -120,7 +120,7 @@ pub const Registry = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn discardIf(self: *GroupData, entity: Entity) void {
|
pub fn discardIf(self: *GroupData, entity: Entity) void {
|
||||||
if (self.owned.len == 0) {
|
if (self.owned.len == 0) {
|
||||||
if (self.entity_set.contains(entity))
|
if (self.entity_set.contains(entity))
|
||||||
self.entity_set.remove(entity);
|
self.entity_set.remove(entity);
|
||||||
@ -608,7 +608,7 @@ pub const Registry = struct {
|
|||||||
inline fn concatTypes(comptime types: var) []const u8 {
|
inline fn concatTypes(comptime types: var) []const u8 {
|
||||||
comptime {
|
comptime {
|
||||||
const impl = struct {
|
const impl = struct {
|
||||||
fn asc(lhs: []const u8, rhs: []const u8) bool {
|
fn asc(context: void, lhs: []const u8, rhs: []const u8) bool {
|
||||||
return std.mem.lessThan(u8, lhs, rhs);
|
return std.mem.lessThan(u8, lhs, rhs);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -618,7 +618,7 @@ pub const Registry = struct {
|
|||||||
name.* = @typeName(types[i]);
|
name.* = @typeName(types[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
std.sort.sort([]const u8, &names, impl.asc);
|
std.sort.sort([]const u8, &names, {}, impl.asc);
|
||||||
|
|
||||||
comptime var res: []const u8 = "";
|
comptime var res: []const u8 = "";
|
||||||
inline for (names) |name| res = res ++ name;
|
inline for (names) |name| res = res ++ name;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const warn = std.debug.warn;
|
const warn = std.debug.warn;
|
||||||
|
const utils = @import("utils.zig");
|
||||||
const ReverseSliceIterator = @import("utils.zig").ReverseSliceIterator;
|
const ReverseSliceIterator = @import("utils.zig").ReverseSliceIterator;
|
||||||
|
|
||||||
// TODO: fix entity_mask. it should come from EntityTraitsDefinition.
|
// TODO: fix entity_mask. it should come from EntityTraitsDefinition.
|
||||||
@ -38,7 +39,7 @@ pub fn SparseSet(comptime SparseT: type) type {
|
|||||||
allocator.destroy(self);
|
allocator.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn page(self: Self, sparse: SparseT) usize {
|
pub fn page(self: Self, sparse: SparseT) usize {
|
||||||
// TODO: support paging
|
// TODO: support paging
|
||||||
// return (sparse & EntityTraits.entity_mask) / sparse_per_page;
|
// return (sparse & EntityTraits.entity_mask) / sparse_per_page;
|
||||||
return sparse & self.entity_mask;
|
return sparse & self.entity_mask;
|
||||||
@ -141,10 +142,19 @@ pub fn SparseSet(comptime SparseT: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sort elements according to the given comparison function
|
/// Sort elements according to the given comparison function
|
||||||
pub fn sort(self: *Self, sortFn: fn (SparseT, SparseT) bool) void {
|
pub fn sort(self: *Self, comptime sortFn: fn (void, SparseT, SparseT) bool) void {
|
||||||
std.sort.insertionSort(SparseT, self.dense.items, sortFn);
|
std.sort.insertionSort(SparseT, self.dense.items, {}, sortFn);
|
||||||
|
|
||||||
|
for (self.dense.items) |sparse| {
|
||||||
|
// self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(SparseT, sparse);
|
||||||
|
self.sparse.items[self.page(sparse)] = @intCast(SparseT, sparse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sort elements according to the given comparison function and keeps sub_items with the same sort
|
||||||
|
pub fn sortSub(self: *Self, comptime sortFn: fn (void, SparseT, SparseT) bool, comptime T: type, sub_items: []T) void {
|
||||||
|
utils.sortSub(SparseT, T, self.dense.items, sub_items, sortFn);
|
||||||
|
|
||||||
var i = @as(usize, 0);
|
|
||||||
for (self.dense.items) |sparse| {
|
for (self.dense.items) |sparse| {
|
||||||
// self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(SparseT, sparse);
|
// self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(SparseT, sparse);
|
||||||
self.sparse.items[self.page(sparse)] = @intCast(SparseT, sparse);
|
self.sparse.items[self.page(sparse)] = @intCast(SparseT, sparse);
|
||||||
@ -259,7 +269,7 @@ test "iterate" {
|
|||||||
set.add(2);
|
set.add(2);
|
||||||
set.add(3);
|
set.add(3);
|
||||||
|
|
||||||
var i: u32 = @intCast(u32, set.len()) - 1;
|
var i: u32 = @intCast(u32, set.len()) - 1;
|
||||||
var iter = set.reverseIterator();
|
var iter = set.reverseIterator();
|
||||||
while (iter.next()) |entity| {
|
while (iter.next()) |entity| {
|
||||||
std.testing.expectEqual(i, entity);
|
std.testing.expectEqual(i, entity);
|
||||||
@ -290,6 +300,8 @@ test "respect 1" {
|
|||||||
std.testing.expectEqual(set1.dense.items[1], set2.dense.items[2]);
|
std.testing.expectEqual(set1.dense.items[1], set2.dense.items[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const desc_u32 = std.sort.desc(u32);
|
||||||
|
|
||||||
test "respect 2" {
|
test "respect 2" {
|
||||||
var set = SparseSet(u32).initPtr(std.testing.allocator);
|
var set = SparseSet(u32).initPtr(std.testing.allocator);
|
||||||
defer set.deinit();
|
defer set.deinit();
|
||||||
@ -300,7 +312,7 @@ test "respect 2" {
|
|||||||
set.add(1);
|
set.add(1);
|
||||||
set.add(3);
|
set.add(3);
|
||||||
|
|
||||||
set.sort(std.sort.desc(u32));
|
set.sort(desc_u32);
|
||||||
|
|
||||||
for (set.dense.items) |item, i| {
|
for (set.dense.items) |item, i| {
|
||||||
if (i < set.dense.items.len - 1) {
|
if (i < set.dense.items.len - 1) {
|
||||||
|
@ -49,13 +49,13 @@ pub fn ReverseSliceIterator(comptime T: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// sorts items using lessThan and keeps sub_items with the same sort
|
/// sorts items using lessThan and keeps sub_items with the same sort
|
||||||
pub fn sortSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T2, lessThan: fn (lhs: T1, rhs: T1) bool) void {
|
pub fn sortSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T2, lessThan: fn (void, lhs: T1, rhs: T1) bool) void {
|
||||||
var i: usize = 1;
|
var i: usize = 1;
|
||||||
while (i < items.len) : (i += 1) {
|
while (i < items.len) : (i += 1) {
|
||||||
const x = items[i];
|
const x = items[i];
|
||||||
const y = sub_items[i];
|
const y = sub_items[i];
|
||||||
var j: usize = i;
|
var j: usize = i;
|
||||||
while (j > 0 and lessThan(x, items[j - 1])) : (j -= 1) {
|
while (j > 0 and lessThan({}, x, items[j - 1])) : (j -= 1) {
|
||||||
items[j] = items[j - 1];
|
items[j] = items[j - 1];
|
||||||
sub_items[j] = sub_items[j - 1];
|
sub_items[j] = sub_items[j - 1];
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,41 @@ const Rotation = struct { x: f32 = 0 };
|
|||||||
fn printStore(store: var, name: []const u8) void {
|
fn printStore(store: var, name: []const u8) void {
|
||||||
warn("--- {} ---\n", .{name});
|
warn("--- {} ---\n", .{name});
|
||||||
for (store.set.dense.items) |e, i| {
|
for (store.set.dense.items) |e, i| {
|
||||||
warn("{:3.0}", .{e});
|
warn("[{}] {}", .{e, store.set.sparse.items[store.set.page(store.set.dense.items[i])]});
|
||||||
warn(" ({d:3.0})", .{store.instances.items[i]});
|
warn(" ({d:.2}) ", .{store.instances.items[i]});
|
||||||
}
|
}
|
||||||
warn("\n", .{});
|
warn("\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const asc_u32 = std.sort.asc(u32);
|
||||||
|
const desc_u32 = std.sort.desc(f32);
|
||||||
|
|
||||||
|
test "sort component" {
|
||||||
|
std.debug.warn("\n", .{});
|
||||||
|
|
||||||
|
var store = ecs.ComponentStorage(f32, u32).initPtr(std.testing.allocator);
|
||||||
|
defer store.deinit();
|
||||||
|
|
||||||
|
store.add(1, @as(f32, 1.1));
|
||||||
|
store.add(2, @as(f32, 2.2));
|
||||||
|
store.add(0, @as(f32, 0.0));
|
||||||
|
|
||||||
|
printStore(store, "Fucker");
|
||||||
|
|
||||||
|
store.sort(u32, asc_u32);
|
||||||
|
for (store.raw()) |e, i| {
|
||||||
|
// std.debug.warn("{}: {}\n", .{i, e});
|
||||||
|
// std.testing.expectEqual(@intCast(u32, i), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
printStore(store, "Fucker");
|
||||||
|
|
||||||
|
store.sort(f32, desc_u32);
|
||||||
|
for (store.raw()) |e, i| {
|
||||||
|
// std.testing.expectEqual(counter, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "nested OwningGroups add/remove components" {
|
test "nested OwningGroups add/remove components" {
|
||||||
var reg = Registry.init(std.testing.allocator);
|
var reg = Registry.init(std.testing.allocator);
|
||||||
defer reg.deinit();
|
defer reg.deinit();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user