|
|
|
@ -1,349 +1,354 @@ |
|
|
|
|
//! Represents the path of an `Entity`, describing its place in the world's |
|
|
|
|
//! hierarchy which is constructed using `ChildOf` relationships. |
|
|
|
|
|
|
|
|
|
const std = @import("std"); |
|
|
|
|
const Allocator = std.mem.Allocator; |
|
|
|
|
|
|
|
|
|
const meta = @import("./meta.zig"); |
|
|
|
|
const Entity = @import("./entity.zig").Entity; |
|
|
|
|
|
|
|
|
|
// TODO: See if we should even be using this? |
|
|
|
|
const native_endian = @import("builtin").cpu.arch.endian(); |
|
|
|
|
|
|
|
|
|
// TODO: Do something better than just `std.debug.assert`. |
|
|
|
|
// TODO: Offer a way to validate paths, like checking for empty parts. |
|
|
|
|
|
|
|
|
|
/// Represents the path of an `Entity`, describing its place in the world's |
|
|
|
|
/// hierarchy which is constructed using `ChildOf` relationships. |
|
|
|
|
pub const Path = struct { |
|
|
|
|
/// Whether the path is specified to be absolute. |
|
|
|
|
/// Note that a relative path can still be interpreted as absolute. |
|
|
|
|
absolute: bool, |
|
|
|
|
/// The string parts that make up the path. |
|
|
|
|
parts: []const EntityPart, |
|
|
|
|
/// The allocator that was used to allocate `parts`. |
|
|
|
|
alloc: ?Allocator = null, |
|
|
|
|
/// Whether the parts outer array itself is owned by this `Path`. |
|
|
|
|
owns_array: bool = false, |
|
|
|
|
/// Whether the parts inner strings are owned by this `Path`. |
|
|
|
|
owns_parts: bool = false, |
|
|
|
|
|
|
|
|
|
/// Represents an `Entity` in a `Path`, either by name or its numeric id. |
|
|
|
|
pub const EntityPart = union(enum) { |
|
|
|
|
id: u32, |
|
|
|
|
name: [:0]const u8, |
|
|
|
|
}; |
|
|
|
|
const Path = @This(); |
|
|
|
|
|
|
|
|
|
/// Whether the path is specified to be absolute. |
|
|
|
|
/// Note that a relative path can still be interpreted as absolute. |
|
|
|
|
absolute: bool, |
|
|
|
|
/// The string parts that make up the path. |
|
|
|
|
parts: []const EntityPart, |
|
|
|
|
/// The allocator that was used to allocate `parts`. |
|
|
|
|
alloc: ?Allocator = null, |
|
|
|
|
/// Whether the parts outer array itself is owned by this `Path`. |
|
|
|
|
owns_array: bool = false, |
|
|
|
|
/// Whether the parts inner strings are owned by this `Path`. |
|
|
|
|
owns_parts: bool = false, |
|
|
|
|
|
|
|
|
|
/// Represents an `Entity` in a `Path`, either by name or its numeric id. |
|
|
|
|
pub const EntityPart = union(enum) { |
|
|
|
|
id: u32, |
|
|
|
|
name: [:0]const u8, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/// Format used to parse and stringify `Path`s. |
|
|
|
|
/// Format used to parse and stringify `Path`s. |
|
|
|
|
/// |
|
|
|
|
/// When this type is formatted, you may use any of the `FormatOption` |
|
|
|
|
/// constants in this type by name as `fmt` specifier, such as `"{unix}"`. |
|
|
|
|
/// An empty `fmt` results in the `default` format being used, which can be |
|
|
|
|
/// changed at runtime. |
|
|
|
|
pub const FormatOptions = struct { |
|
|
|
|
/// The root separator used for an absolute `Path`, if any. |
|
|
|
|
/// |
|
|
|
|
/// When this type is formatted, you may use any of the `FormatOption` |
|
|
|
|
/// constants in this type by name as `fmt` specifier, such as `"{unix}"`. |
|
|
|
|
/// An empty `fmt` results in the `default` format being used, which can be |
|
|
|
|
/// changed at runtime. |
|
|
|
|
pub const FormatOptions = struct { |
|
|
|
|
/// The root separator used for an absolute `Path`, if any. |
|
|
|
|
/// |
|
|
|
|
/// If set to `null`, absolute paths can't be represented using strings. |
|
|
|
|
/// In this case, an absolue path and relative path with identical parts |
|
|
|
|
/// will be indistinguishable. |
|
|
|
|
root_sep: ?[]const u8, |
|
|
|
|
/// The separator used between parts that make up a `Path`. |
|
|
|
|
sep: []const u8, |
|
|
|
|
|
|
|
|
|
/// The format used by Flecs' C API. For example `flecs.core`. |
|
|
|
|
pub const flecs_c = FormatOptions{ .root_sep = null, .sep = "." }; |
|
|
|
|
/// The format used by Flecs' C++ API. For example `::flecs::core`. |
|
|
|
|
pub const flecs_cpp = FormatOptions{ .root_sep = "::", .sep = "::" }; |
|
|
|
|
/// Unix-like format. For example `/flecs/core`. |
|
|
|
|
pub const unix = FormatOptions{ .root_sep = "/", .sep = "/" }; |
|
|
|
|
|
|
|
|
|
/// The default format used when none is specified. Can be changed at runtime. |
|
|
|
|
pub var default = flecs_c; |
|
|
|
|
}; |
|
|
|
|
/// If set to `null`, absolute paths can't be represented using strings. |
|
|
|
|
/// In this case, an absolue path and relative path with identical parts |
|
|
|
|
/// will be indistinguishable. |
|
|
|
|
root_sep: ?[]const u8, |
|
|
|
|
/// The separator used between parts that make up a `Path`. |
|
|
|
|
sep: []const u8, |
|
|
|
|
|
|
|
|
|
/// The format used by Flecs' C API. For example `flecs.core`. |
|
|
|
|
pub const flecs_c = FormatOptions{ .root_sep = null, .sep = "." }; |
|
|
|
|
/// The format used by Flecs' C++ API. For example `::flecs::core`. |
|
|
|
|
pub const flecs_cpp = FormatOptions{ .root_sep = "::", .sep = "::" }; |
|
|
|
|
/// Unix-like format. For example `/flecs/core`. |
|
|
|
|
pub const unix = FormatOptions{ .root_sep = "/", .sep = "/" }; |
|
|
|
|
|
|
|
|
|
/// The default format used when none is specified. Can be changed at runtime. |
|
|
|
|
pub var default = flecs_c; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/// Creates an array of `EntityPath`s with known size equal to the number of |
|
|
|
|
/// elements in the specified tuple argument. Each element of the tuple is |
|
|
|
|
/// either converted to a `.name` part, or an `.id` part. |
|
|
|
|
/// |
|
|
|
|
/// Keep in mind the mutability and lifetime of the string elements passed |
|
|
|
|
/// to this function, as they aren't cloned and ownership stays the same. |
|
|
|
|
/// In many cases, the lifetime of `Path`s is relatively short. When this |
|
|
|
|
/// is not the case, it's recommended to `.clone()` the path after creation. |
|
|
|
|
pub fn buildParts(parts: anytype) [numElements(parts)]EntityPart { |
|
|
|
|
if (comptime !std.meta.trait.isTuple(@TypeOf(parts))) |
|
|
|
|
@compileError("Expected tuple, got '" ++ @typeName(@TypeOf(parts)) ++ "'"); |
|
|
|
|
var result: [numElements(parts)]EntityPart = undefined; |
|
|
|
|
inline for (&result, parts) |*res, part| { |
|
|
|
|
res.* = if (comptime std.meta.trait.isZigString(@TypeOf(part))) |
|
|
|
|
.{ .name = part } |
|
|
|
|
else switch (@typeInfo(@TypeOf(part))) { |
|
|
|
|
.Int, .ComptimeInt => .{ .id = part }, |
|
|
|
|
else => @compileError("Expected '[:0]const u8' or 'u32', got '" ++ @typeName(@TypeOf(part)) ++ "'"), |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
/// Creates an array of `EntityPath`s with known size equal to the number of |
|
|
|
|
/// elements in the specified tuple argument. Each element of the tuple is |
|
|
|
|
/// either converted to a `.name` part, or an `.id` part. |
|
|
|
|
/// |
|
|
|
|
/// Keep in mind the mutability and lifetime of the string elements passed |
|
|
|
|
/// to this function, as they aren't cloned and ownership stays the same. |
|
|
|
|
/// In many cases, the lifetime of `Path`s is relatively short. When this |
|
|
|
|
/// is not the case, it's recommended to `.clone()` the path after creation. |
|
|
|
|
pub fn buildParts(parts: anytype) [numElements(parts)]EntityPart { |
|
|
|
|
if (comptime !meta.isTuple(@TypeOf(parts))) |
|
|
|
|
@compileError("Expected tuple, got '" ++ @typeName(@TypeOf(parts)) ++ "'"); |
|
|
|
|
var result: [numElements(parts)]EntityPart = undefined; |
|
|
|
|
inline for (&result, parts) |*res, part| { |
|
|
|
|
res.* = if (comptime meta.isZigString(@TypeOf(part))) |
|
|
|
|
.{ .name = part } |
|
|
|
|
else switch (@typeInfo(@TypeOf(part))) { |
|
|
|
|
.Int, .ComptimeInt => .{ .id = part }, |
|
|
|
|
else => @compileError("Expected '[:0]const u8' or 'u32', got '" ++ @typeName(@TypeOf(part)) ++ "'"), |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns the number of elements of the specified tuple type. |
|
|
|
|
fn numElements(parts: anytype) usize { |
|
|
|
|
return if (comptime std.meta.trait.isTuple(@TypeOf(parts))) |
|
|
|
|
@typeInfo(@TypeOf(parts)).Struct.fields.len |
|
|
|
|
else |
|
|
|
|
0; |
|
|
|
|
} |
|
|
|
|
/// Returns the number of elements of the specified tuple type. |
|
|
|
|
fn numElements(parts: anytype) usize { |
|
|
|
|
return if (comptime meta.isTuple(@TypeOf(parts))) |
|
|
|
|
@typeInfo(@TypeOf(parts)).Struct.fields.len |
|
|
|
|
else |
|
|
|
|
0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Creates a new `Path` from the specified parts. |
|
|
|
|
/// |
|
|
|
|
/// The resulting path does not own any of the given slices. |
|
|
|
|
pub fn fromParts(absolute: bool, parts: []const EntityPart) Path { |
|
|
|
|
std.debug.assert(parts.len > 0); |
|
|
|
|
return .{ .absolute = absolute, .parts = parts }; |
|
|
|
|
} |
|
|
|
|
/// Creates a new `Path` from the specified parts. |
|
|
|
|
/// |
|
|
|
|
/// The resulting path does not own any of the given slices. |
|
|
|
|
pub fn fromParts(absolute: bool, parts: []const EntityPart) Path { |
|
|
|
|
std.debug.assert(parts.len > 0); |
|
|
|
|
return .{ .absolute = absolute, .parts = parts }; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Parses a string as a `Path` using the `FormatOptions` specified, |
|
|
|
|
/// or `FormatOptions.default` if the argument is `null`. |
|
|
|
|
/// |
|
|
|
|
/// If the string starts with the specified root separator (if any), the |
|
|
|
|
/// resulting path will be absolute. The rest of the string will be split |
|
|
|
|
/// by the specified seperator, becoming its parts. |
|
|
|
|
/// |
|
|
|
|
/// This function will allocate duplicate strings taken from the specified |
|
|
|
|
/// source `path`, to ensure they are sentinel-terminated. The resulting |
|
|
|
|
/// `Path` takes ownership of these. |
|
|
|
|
pub fn fromString(path: []const u8, options: ?FormatOptions, alloc: Allocator) !Path { |
|
|
|
|
if (path.len == 0) return error.MustNotBeEmpty; |
|
|
|
|
const opt = options orelse FormatOptions.default; |
|
|
|
|
|
|
|
|
|
var remaining = path; |
|
|
|
|
var absolute = false; |
|
|
|
|
|
|
|
|
|
// If `root_sep` is set and path starts with it, the path is absolute. |
|
|
|
|
if (opt.root_sep) |p| { |
|
|
|
|
if (std.mem.startsWith(u8, remaining, p)) { |
|
|
|
|
remaining = remaining[p.len..]; |
|
|
|
|
absolute = true; |
|
|
|
|
} |
|
|
|
|
/// Parses a string as a `Path` using the `FormatOptions` specified, |
|
|
|
|
/// or `FormatOptions.default` if the argument is `null`. |
|
|
|
|
/// |
|
|
|
|
/// If the string starts with the specified root separator (if any), the |
|
|
|
|
/// resulting path will be absolute. The rest of the string will be split |
|
|
|
|
/// by the specified seperator, becoming its parts. |
|
|
|
|
/// |
|
|
|
|
/// This function will allocate duplicate strings taken from the specified |
|
|
|
|
/// source `path`, to ensure they are sentinel-terminated. The resulting |
|
|
|
|
/// `Path` takes ownership of these. |
|
|
|
|
pub fn fromString(path: []const u8, options: ?FormatOptions, alloc: Allocator) !Path { |
|
|
|
|
if (path.len == 0) return error.MustNotBeEmpty; |
|
|
|
|
const opt = options orelse FormatOptions.default; |
|
|
|
|
|
|
|
|
|
var remaining = path; |
|
|
|
|
var absolute = false; |
|
|
|
|
|
|
|
|
|
// If `root_sep` is set and path starts with it, the path is absolute. |
|
|
|
|
if (opt.root_sep) |p| { |
|
|
|
|
if (std.mem.startsWith(u8, remaining, p)) { |
|
|
|
|
remaining = remaining[p.len..]; |
|
|
|
|
absolute = true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const parts_len = std.mem.count(u8, remaining, opt.sep) + 1; |
|
|
|
|
const parts = try alloc.alloc(EntityPart, parts_len); |
|
|
|
|
|
|
|
|
|
var i: usize = 0; |
|
|
|
|
var it = std.mem.splitSequence(u8, remaining, opt.sep); |
|
|
|
|
while (it.next()) |str| : (i += 1) |
|
|
|
|
parts[i] = if (parseNumericId(str)) |id| |
|
|
|
|
.{ .id = id } |
|
|
|
|
else |
|
|
|
|
.{ .name = try alloc.dupeZ(u8, str) }; |
|
|
|
|
|
|
|
|
|
return .{ |
|
|
|
|
.absolute = absolute, |
|
|
|
|
.parts = parts, |
|
|
|
|
.alloc = alloc, |
|
|
|
|
.owns_array = true, |
|
|
|
|
.owns_parts = true, |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Creates a `Path` for the specified `child` entity, optionally in |
|
|
|
|
/// relation to the specified `parent` entity. If `parent` is not `null`, |
|
|
|
|
/// the resulting path is relative. Otherwise it will be absolute. |
|
|
|
|
/// |
|
|
|
|
/// This function allocates an array for the parts that make up the entity's |
|
|
|
|
/// path, however each part itself is owned by Flecs and could change or be |
|
|
|
|
/// invalidated ay any time, such as when an entity is renamed or removed. |
|
|
|
|
pub fn fromEntity(comptime ctx: type, parent: ?Entity(ctx), child: Entity(ctx), alloc: Allocator) !Path { |
|
|
|
|
std.debug.assert(child.raw != 0); |
|
|
|
|
if (parent) |p| { |
|
|
|
|
std.debug.assert(p.raw != 0); |
|
|
|
|
std.debug.assert(p.raw != child.raw); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO: Use a threadlocal field with reasonable size, then clone the result. |
|
|
|
|
const starting_capacity: usize = 12; |
|
|
|
|
var parts = try alloc.alloc(EntityPart, starting_capacity); |
|
|
|
|
errdefer alloc.free(parts); |
|
|
|
|
var num_parts: usize = 0; |
|
|
|
|
|
|
|
|
|
// Traverse up the entity hierarchy starting from the specified child |
|
|
|
|
// entity up until either the specified parent or root of the hierarchy. |
|
|
|
|
var current = child; |
|
|
|
|
while (true) { |
|
|
|
|
std.debug.assert(num_parts == 0 or current.raw != child.raw); // Cycle detected. |
|
|
|
|
parts[num_parts] = if (current.getName()) |name| |
|
|
|
|
.{ .name = name } |
|
|
|
|
else |
|
|
|
|
.{ .id = current.getEntityId() }; |
|
|
|
|
num_parts += 1; |
|
|
|
|
|
|
|
|
|
// Move to the parent entity, if any. |
|
|
|
|
current = current.getParent() orelse |
|
|
|
|
// If `parent` wasn't specified, we reached the root. Done. |
|
|
|
|
// Otherwise, if the parent wasn't found, return an error. |
|
|
|
|
if (parent == null) break else return error.ParentNotFound; |
|
|
|
|
|
|
|
|
|
// If we reached the specified `parent`, we're done here! |
|
|
|
|
if (parent != null and current.raw == parent.?.raw) break; |
|
|
|
|
} |
|
|
|
|
const parts_len = std.mem.count(u8, remaining, opt.sep) + 1; |
|
|
|
|
const parts = try alloc.alloc(EntityPart, parts_len); |
|
|
|
|
|
|
|
|
|
parts = try alloc.realloc(parts, num_parts); |
|
|
|
|
std.mem.reverse(EntityPart, parts); |
|
|
|
|
var i: usize = 0; |
|
|
|
|
var it = std.mem.splitSequence(u8, remaining, opt.sep); |
|
|
|
|
while (it.next()) |str| : (i += 1) |
|
|
|
|
parts[i] = if (parseNumericId(str)) |id| |
|
|
|
|
.{ .id = id } |
|
|
|
|
else |
|
|
|
|
.{ .name = try alloc.dupeZ(u8, str) }; |
|
|
|
|
|
|
|
|
|
return .{ |
|
|
|
|
.absolute = absolute, |
|
|
|
|
.parts = parts, |
|
|
|
|
.alloc = alloc, |
|
|
|
|
.owns_array = true, |
|
|
|
|
.owns_parts = true, |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return .{ |
|
|
|
|
.absolute = parent != null, |
|
|
|
|
.parts = parts, |
|
|
|
|
.alloc = alloc, |
|
|
|
|
.owns_array = true, |
|
|
|
|
}; |
|
|
|
|
/// Creates a `Path` for the specified `child` entity, optionally in |
|
|
|
|
/// relation to the specified `parent` entity. If `parent` is not `null`, |
|
|
|
|
/// the resulting path is relative. Otherwise it will be absolute. |
|
|
|
|
/// |
|
|
|
|
/// This function allocates an array for the parts that make up the entity's |
|
|
|
|
/// path, however each part itself is owned by Flecs and could change or be |
|
|
|
|
/// invalidated ay any time, such as when an entity is renamed or removed. |
|
|
|
|
pub fn fromEntity(comptime ctx: type, parent: ?Entity(ctx), child: Entity(ctx), alloc: Allocator) !Path { |
|
|
|
|
std.debug.assert(child.raw != 0); |
|
|
|
|
if (parent) |p| { |
|
|
|
|
std.debug.assert(p.raw != 0); |
|
|
|
|
std.debug.assert(p.raw != child.raw); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Creates a deep clone of this `Path` using the specified `Allocator`. |
|
|
|
|
pub fn clone(orig: Path, alloc: Allocator) !Path { |
|
|
|
|
var parts = try alloc.dupe(EntityPart, orig.parts); |
|
|
|
|
errdefer alloc.free(parts); |
|
|
|
|
|
|
|
|
|
var num_allocated: usize = 0; |
|
|
|
|
errdefer for (parts[0..num_allocated]) |part| |
|
|
|
|
if (part == .name) alloc.free(part.name); |
|
|
|
|
// TODO: Use a threadlocal field with reasonable size, then clone the result. |
|
|
|
|
const starting_capacity: usize = 12; |
|
|
|
|
var parts = try alloc.alloc(EntityPart, starting_capacity); |
|
|
|
|
errdefer alloc.free(parts); |
|
|
|
|
var num_parts: usize = 0; |
|
|
|
|
|
|
|
|
|
// Traverse up the entity hierarchy starting from the specified child |
|
|
|
|
// entity up until either the specified parent or root of the hierarchy. |
|
|
|
|
var current = child; |
|
|
|
|
while (true) { |
|
|
|
|
std.debug.assert(num_parts == 0 or current.raw != child.raw); // Cycle detected. |
|
|
|
|
parts[num_parts] = if (current.getName()) |name| |
|
|
|
|
.{ .name = name } |
|
|
|
|
else |
|
|
|
|
.{ .id = current.getEntityId() }; |
|
|
|
|
num_parts += 1; |
|
|
|
|
|
|
|
|
|
for (parts) |*part| { |
|
|
|
|
if (part.* == .name) |
|
|
|
|
part.* = .{ .name = try alloc.dupeZ(u8, part.name) }; |
|
|
|
|
num_allocated += 1; |
|
|
|
|
} |
|
|
|
|
// Move to the parent entity, if any. |
|
|
|
|
current = current.getParent() orelse |
|
|
|
|
// If `parent` wasn't specified, we reached the root. Done. |
|
|
|
|
// Otherwise, if the parent wasn't found, return an error. |
|
|
|
|
if (parent == null) break else return error.ParentNotFound; |
|
|
|
|
|
|
|
|
|
return .{ |
|
|
|
|
.absolute = orig.absolute, |
|
|
|
|
.parts = parts, |
|
|
|
|
.alloc = alloc, |
|
|
|
|
.owns_array = true, |
|
|
|
|
.owns_parts = true, |
|
|
|
|
}; |
|
|
|
|
// If we reached the specified `parent`, we're done here! |
|
|
|
|
if (parent != null and current.raw == parent.?.raw) break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Destroys any memory owned by this `Path`, if any. |
|
|
|
|
pub fn deinit(self: Path) void { |
|
|
|
|
if (self.owns_parts) |
|
|
|
|
for (self.parts) |part| |
|
|
|
|
if (part == .name) |
|
|
|
|
self.alloc.?.free(part.name); |
|
|
|
|
if (self.owns_array) |
|
|
|
|
self.alloc.?.free(self.parts); |
|
|
|
|
} |
|
|
|
|
parts = try alloc.realloc(parts, num_parts); |
|
|
|
|
std.mem.reverse(EntityPart, parts); |
|
|
|
|
|
|
|
|
|
pub fn toString( |
|
|
|
|
self: Path, |
|
|
|
|
options: ?FormatOptions, |
|
|
|
|
alloc: Allocator, |
|
|
|
|
) ![:0]const u8 { |
|
|
|
|
const opt = options orelse FormatOptions.default; |
|
|
|
|
const length = self.calculateLength(opt); |
|
|
|
|
var result = try alloc.allocSentinel(u8, length, 0); |
|
|
|
|
var stream = std.io.fixedBufferStream(result); |
|
|
|
|
try write(self, opt, stream.writer()); |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
return .{ |
|
|
|
|
.absolute = parent != null, |
|
|
|
|
.parts = parts, |
|
|
|
|
.alloc = alloc, |
|
|
|
|
.owns_array = true, |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn format( |
|
|
|
|
self: Path, |
|
|
|
|
comptime fmt: []const u8, |
|
|
|
|
options: std.fmt.FormatOptions, |
|
|
|
|
writer: anytype, |
|
|
|
|
) !void { |
|
|
|
|
_ = options; // TODO: Actually make use of this. |
|
|
|
|
|
|
|
|
|
const opt = if (fmt.len == 0) |
|
|
|
|
FormatOptions.default |
|
|
|
|
else if (@hasDecl(FormatOptions, fmt) and @TypeOf(@field(FormatOptions, fmt)) == FormatOptions) |
|
|
|
|
@field(FormatOptions, fmt) |
|
|
|
|
else |
|
|
|
|
std.fmt.invalidFmtError(fmt, Path); |
|
|
|
|
/// Creates a deep clone of this `Path` using the specified `Allocator`. |
|
|
|
|
pub fn clone(orig: Path, alloc: Allocator) !Path { |
|
|
|
|
var parts = try alloc.dupe(EntityPart, orig.parts); |
|
|
|
|
errdefer alloc.free(parts); |
|
|
|
|
|
|
|
|
|
try self.write(opt, writer); |
|
|
|
|
} |
|
|
|
|
var num_allocated: usize = 0; |
|
|
|
|
errdefer for (parts[0..num_allocated]) |part| |
|
|
|
|
if (part == .name) alloc.free(part.name); |
|
|
|
|
|
|
|
|
|
/// Returns whether the contents of the specified `Path`s are equivalent. |
|
|
|
|
/// |
|
|
|
|
/// Path equivalency does not imply these paths are or are not referring to |
|
|
|
|
/// the same `Entity`. For example, an entity that is referred to using its |
|
|
|
|
/// entity id has a different path from the same entity referred to by name. |
|
|
|
|
pub fn equals(first: Path, second: Path) bool { |
|
|
|
|
if (first.absolute != second.absolute) return false; |
|
|
|
|
if (first.parts.len != second.parts.len) return false; |
|
|
|
|
for (first.parts, second.parts) |a, b| switch (a) { |
|
|
|
|
.id => |a_id| if (b != .id or a_id != b.id) return false, |
|
|
|
|
.name => |a_name| if (b != .name or !std.mem.eql(u8, a_name, b.name)) return false, |
|
|
|
|
}; |
|
|
|
|
return true; |
|
|
|
|
for (parts) |*part| { |
|
|
|
|
if (part.* == .name) |
|
|
|
|
part.* = .{ .name = try alloc.dupeZ(u8, part.name) }; |
|
|
|
|
num_allocated += 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn calculateLength(self: Path, opt: FormatOptions) usize { |
|
|
|
|
// Separators. |
|
|
|
|
var result = opt.sep.len * (self.parts.len - 1); |
|
|
|
|
// Root separator. |
|
|
|
|
if (self.absolute) { |
|
|
|
|
if (opt.root_sep) |p| |
|
|
|
|
result += p.len; |
|
|
|
|
} |
|
|
|
|
// Parts themselves. |
|
|
|
|
return .{ |
|
|
|
|
.absolute = orig.absolute, |
|
|
|
|
.parts = parts, |
|
|
|
|
.alloc = alloc, |
|
|
|
|
.owns_array = true, |
|
|
|
|
.owns_parts = true, |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Destroys any memory owned by this `Path`, if any. |
|
|
|
|
pub fn deinit(self: Path) void { |
|
|
|
|
if (self.owns_parts) |
|
|
|
|
for (self.parts) |part| |
|
|
|
|
result += switch (part) { |
|
|
|
|
.id => |id| numDigits(id), |
|
|
|
|
.name => |name| name.len, |
|
|
|
|
}; |
|
|
|
|
return result; |
|
|
|
|
if (part == .name) |
|
|
|
|
self.alloc.?.free(part.name); |
|
|
|
|
if (self.owns_array) |
|
|
|
|
self.alloc.?.free(self.parts); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn toString( |
|
|
|
|
self: Path, |
|
|
|
|
options: ?FormatOptions, |
|
|
|
|
alloc: Allocator, |
|
|
|
|
) ![:0]const u8 { |
|
|
|
|
const opt = options orelse FormatOptions.default; |
|
|
|
|
const length = self.calculateLength(opt); |
|
|
|
|
const result = try alloc.allocSentinel(u8, length, 0); |
|
|
|
|
var stream = std.io.fixedBufferStream(result); |
|
|
|
|
try write(self, opt, stream.writer()); |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn format( |
|
|
|
|
self: Path, |
|
|
|
|
comptime fmt: []const u8, |
|
|
|
|
options: std.fmt.FormatOptions, |
|
|
|
|
writer: anytype, |
|
|
|
|
) !void { |
|
|
|
|
_ = options; // TODO: Actually make use of this. |
|
|
|
|
|
|
|
|
|
const opt = if (fmt.len == 0) |
|
|
|
|
FormatOptions.default |
|
|
|
|
else if (@hasDecl(FormatOptions, fmt) and @TypeOf(@field(FormatOptions, fmt)) == FormatOptions) |
|
|
|
|
@field(FormatOptions, fmt) |
|
|
|
|
else |
|
|
|
|
std.fmt.invalidFmtError(fmt, Path); |
|
|
|
|
|
|
|
|
|
try self.write(opt, writer); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns whether the contents of the specified `Path`s are equivalent. |
|
|
|
|
/// |
|
|
|
|
/// Path equivalency does not imply these paths are or are not referring to |
|
|
|
|
/// the same `Entity`. For example, an entity that is referred to using its |
|
|
|
|
/// entity id has a different path from the same entity referred to by name. |
|
|
|
|
pub fn equals(first: Path, second: Path) bool { |
|
|
|
|
if (first.absolute != second.absolute) return false; |
|
|
|
|
if (first.parts.len != second.parts.len) return false; |
|
|
|
|
for (first.parts, second.parts) |a, b| switch (a) { |
|
|
|
|
.id => |a_id| if (b != .id or a_id != b.id) return false, |
|
|
|
|
.name => |a_name| if (b != .name or !std.mem.eql(u8, a_name, b.name)) return false, |
|
|
|
|
}; |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn calculateLength(self: Path, opt: FormatOptions) usize { |
|
|
|
|
// Separators. |
|
|
|
|
var result = opt.sep.len * (self.parts.len - 1); |
|
|
|
|
// Root separator. |
|
|
|
|
if (self.absolute) { |
|
|
|
|
if (opt.root_sep) |p| |
|
|
|
|
result += p.len; |
|
|
|
|
} |
|
|
|
|
// Parts themselves. |
|
|
|
|
for (self.parts) |part| |
|
|
|
|
result += switch (part) { |
|
|
|
|
.id => |id| numDigits(id), |
|
|
|
|
.name => |name| name.len, |
|
|
|
|
}; |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn write(self: Path, opt: FormatOptions, writer: anytype) !void { |
|
|
|
|
// Write root separator (if applicable). |
|
|
|
|
if (self.absolute) { |
|
|
|
|
if (opt.root_sep) |p| |
|
|
|
|
try writer.writeAll(p); |
|
|
|
|
} |
|
|
|
|
// Write the first part. |
|
|
|
|
switch (self.parts[0]) { |
|
|
|
|
.id => |id| try writer.writeIntNative(u32, id), |
|
|
|
|
fn write(self: Path, opt: FormatOptions, writer: anytype) !void { |
|
|
|
|
// Write root separator (if applicable). |
|
|
|
|
if (self.absolute) { |
|
|
|
|
if (opt.root_sep) |p| |
|
|
|
|
try writer.writeAll(p); |
|
|
|
|
} |
|
|
|
|
// Write the first part. |
|
|
|
|
switch (self.parts[0]) { |
|
|
|
|
.id => |id| try writer.writeInt(u32, id, native_endian), |
|
|
|
|
.name => |name| try writer.writeAll(name), |
|
|
|
|
} |
|
|
|
|
// Write the remaining parts, each preceeded bu separator. |
|
|
|
|
for (self.parts[1..]) |part| { |
|
|
|
|
try writer.writeAll(opt.sep); |
|
|
|
|
switch (part) { |
|
|
|
|
.id => |id| try writer.writeInt(u32, id, native_endian), |
|
|
|
|
.name => |name| try writer.writeAll(name), |
|
|
|
|
} |
|
|
|
|
// Write the remaining parts, each preceeded bu separator. |
|
|
|
|
for (self.parts[1..]) |part| { |
|
|
|
|
try writer.writeAll(opt.sep); |
|
|
|
|
switch (part) { |
|
|
|
|
.id => |id| try writer.writeIntNative(u32, id), |
|
|
|
|
.name => |name| try writer.writeAll(name), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Attempts to parse the specified string as a numeric entity id. |
|
|
|
|
fn parseNumericId(str: []const u8) ?u32 { |
|
|
|
|
if (str.len == 0 or str.len > 10) return null; |
|
|
|
|
var result: u32 = 0; |
|
|
|
|
var place: u32 = 1; |
|
|
|
|
var it = std.mem.reverseIterator(str); |
|
|
|
|
while (it.next()) |chr| { |
|
|
|
|
const d = std.fmt.charToDigit(chr, 10) catch return null; |
|
|
|
|
result = std.math.add(u32, result, d * place) catch return null; |
|
|
|
|
place *%= 10; // Wrapping to avoid overflow error. |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
/// Attempts to parse the specified string as a numeric entity id. |
|
|
|
|
fn parseNumericId(str: []const u8) ?u32 { |
|
|
|
|
if (str.len == 0 or str.len > 10) return null; |
|
|
|
|
var result: u32 = 0; |
|
|
|
|
var place: u32 = 1; |
|
|
|
|
var it = std.mem.reverseIterator(str); |
|
|
|
|
while (it.next()) |chr| { |
|
|
|
|
const d = std.fmt.charToDigit(chr, 10) catch return null; |
|
|
|
|
result = std.math.add(u32, result, d * place) catch return null; |
|
|
|
|
place *%= 10; // Wrapping to avoid overflow error. |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Calculates the numbers of digits of an entity id when formatted as a |
|
|
|
|
/// string. Since entity ids tend to be small this is technically optimized |
|
|
|
|
/// for smaller numbers but performance likely doesn't matter much. |
|
|
|
|
fn numDigits(n: u32) usize { |
|
|
|
|
// zig fmt: off |
|
|
|
|
return if (n < 10) 1 |
|
|
|
|
else if (n < 100) 2 |
|
|
|
|
else if (n < 1000) 3 |
|
|
|
|
else if (n < 10000) 4 |
|
|
|
|
else if (n < 100000) 5 |
|
|
|
|
else if (n < 1000000) 6 |
|
|
|
|
else if (n < 10000000) 7 |
|
|
|
|
else if (n < 100000000) 8 |
|
|
|
|
else if (n < 1000000000) 9 |
|
|
|
|
else 10; |
|
|
|
|
// zig fmt: on |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
/// Calculates the numbers of digits of an entity id when formatted as a |
|
|
|
|
/// string. Since entity ids tend to be small this is technically optimized |
|
|
|
|
/// for smaller numbers but performance likely doesn't matter much. |
|
|
|
|
fn numDigits(n: u32) usize { |
|
|
|
|
// zig fmt: off |
|
|
|
|
return if (n < 10) 1 |
|
|
|
|
else if (n < 100) 2 |
|
|
|
|
else if (n < 1000) 3 |
|
|
|
|
else if (n < 10000) 4 |
|
|
|
|
else if (n < 100000) 5 |
|
|
|
|
else if (n < 1000000) 6 |
|
|
|
|
else if (n < 10000000) 7 |
|
|
|
|
else if (n < 100000000) 8 |
|
|
|
|
else if (n < 1000000000) 9 |
|
|
|
|
else 10; |
|
|
|
|
// zig fmt: on |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
test Path { |
|
|
|
|
const alloc = std.testing.allocator; |
|
|
|
|
const expect = @import("./expect.zig"); |
|
|
|
|
const expect = @import("./test/expect.zig"); |
|
|
|
|
|
|
|
|
|
// Paths may be constructed by parsing strings. |
|
|
|
|
const relative = try Path.fromString("some.relative.path", null, alloc); |
|
|
|
@ -414,6 +419,8 @@ test Path { |
|
|
|
|
|
|
|
|
|
// The default `FormatOptions` may be changed. |
|
|
|
|
Path.FormatOptions.default = Path.FormatOptions.unix; |
|
|
|
|
// But be sure to unset them after this test so other tests can succeed. |
|
|
|
|
defer Path.FormatOptions.default = Path.FormatOptions.flecs_c; |
|
|
|
|
|
|
|
|
|
// This affects functions that use them to parse or format strings. |
|
|
|
|
try expect.fmt("some/relative/path", "{}", .{relative}); |
|
|
|
|