const std = @import ( " std " ) ;
const c = @import ( " ./c.zig " ) ;
const meta = @import ( " ./meta.zig " ) ;
pub fn Context ( comptime ctx : anytype ) type {
return struct {
pub const Entity = @import ( " ./entity.zig " ) . Entity ( ctx ) ;
pub const Id = @import ( " ./id.zig " ) . Id ( ctx ) ;
pub const Iter = @import ( " ./iter.zig " ) . Iter ( ctx ) ;
pub const Pair = @import ( " ./pair.zig " ) . Pair ( ctx ) ;
pub const World = @import ( " ./world.zig " ) . World ( ctx ) ;
/// Looks up an entity ID unique to this `Context` for the provided
/// type that has been registered previously, typically done by
/// calling functions such as `World.component(...)`.
pub fn lookup ( comptime T : type ) c . ecs_entity_t {
const result = lookupMut ( T ) . * ;
if ( result = = 0 ) std . debug . panic ( " No lookup for {s} " , . { @typeName ( T ) } ) ;
return result ;
}
/// Returns a pointer unique to this `Context` and the provided type
/// that holds an entity ID. Useful for associating types to entities.
pub fn lookupMut ( comptime T : type ) * c . ecs_entity_t {
const EntityHolder = struct {
comptime {
// Capture `ctx` and `T` to create a unique struct.
_ = . { ctx , T } ;
}
pub var id : c . ecs_entity_t = 0 ;
} ;
return & EntityHolder . id ;
}
/// Converts the specified value to an `ecs_entity_t`.
///
/// If `null` is passed to this function, either directly or through an
/// optional type of one of the supported types, `0` is returned. It's up to
/// the caller to do any necessary testing for `0` values, and whether the
/// returned entity is valid or alive.
///
/// The following can be converted:
/// - `Entity` and `ecs_entity_t` - Self-explanatory.
/// - `type` - Looks up the entity associated with that type.
pub fn anyToEntity ( value : anytype ) c . ecs_entity_t {
return switch ( @TypeOf ( value ) ) {
@TypeOf ( null ) = > 0 ,
c . ecs_entity_t = > value ,
Entity = > value . raw ,
? Entity = > if ( value ) | v | v . raw else 0 ,
type = > lookup ( value ) ,
else = > @compileError ( " Value of type " + + @typeName ( @TypeOf ( value ) ) + + " can't be converted to Entity " ) ,
} ;
}
/// Converts the specified value to an `ecs_id_t`.
///
/// If `null` is passed to this function, either directly or through an
/// optional type of one of the supported types, `0` is returned. It's up to
/// the caller to do any necessary testing for `0` values, and whether the
/// returned id is valid.
///
/// The following can be converted:
/// - `Id` and `ecs_id_it` - Self-explanatory.
/// - `Entity` and `ecs_entity_t` - Self-explanatory.
/// - `Pair` - Converts to the equivalent `Id`.
/// - `.{ relation, target }` - A `Pair`, converted using `anyToEntity()`.
/// - `type` - Looks up the entity associated with that type.
pub fn anyToId ( value : anytype ) c . ecs_id_t {
const T = @TypeOf ( value ) ;
if ( comptime meta . isTuple ( T ) ) {
if ( @typeInfo ( T ) . Struct . fields . len ! = 2 )
@compileError ( " Value of type " + + @typeName ( T ) + + " must be a tuple with 2 elements, to be a Pair " ) ;
const relation = anyToEntity ( value [ 0 ] ) ;
const target = anyToEntity ( value [ 1 ] ) ;
return c . ecs_make_pair ( relation , target ) ;
}
return switch ( T ) {
@TypeOf ( null ) = > 0 ,
c . ecs_id_t = > value ,
Id = > value . raw ,
? Id = > if ( value ) | v | v . raw else 0 ,
Pair = > value . raw ,
? Pair = > if ( value ) | v | v . raw else 0 ,
// Technically same type as `ecs_id_it`.
// c.ecs_entity_t => value,
Entity = > value . raw ,
? Entity = > if ( value ) | v | v . raw else 0 ,
type = > lookup ( value ) ,
else = > @compileError ( " Value of type " + + @typeName ( T ) + + " can't be converted to Id " ) ,
} ;
}
/// Registers type lookups for the builtin Flecs entities.
/// These are defined in the `./src/builtin/` directory.
pub fn registerFlecsLookups ( world : * World ) ! void {
const error_writer = std . io . getStdErr ( ) . writer ( ) ;
const flecs = @import ( " ./builtin/flecs.zig " ) ;
const root = try lookupAndRegister ( world , null , " flecs " , flecs , error_writer ) ;
try lookupAndRegisterDeclarations ( world , root , flecs , error_writer ) ;
}
/// Registers type lookups for the builtin Flecs entities, but limited
/// to the `flecs.core` namespace. This is used by `World.initMinimal()`.
pub fn registerFlecsCoreLookups ( world : * World ) ! void {
const error_writer = std . io . getStdErr ( ) . writer ( ) ;
const flecs = @import ( " ./builtin/flecs.zig " ) ;
const flecs_core = @import ( " ./builtin/flecs.core.zig " ) ;
const root = try lookupAndRegister ( world , null , " flecs " , flecs , error_writer ) ;
const core = try lookupAndRegister ( world , root , " core " , flecs_core , error_writer ) ;
try lookupAndRegisterDeclarations ( world , core , flecs_core , error_writer ) ;
}
fn lookupAndRegister (
world : * World ,
parent : ? Entity ,
comptime name : [ ] const u8 ,
comptime T : type ,
error_writer : anytype ,
) ! Entity {
if ( world . lookupChild ( parent , name ) ) | entity | {
const id_ptr = lookupMut ( T ) ;
if ( id_ptr . * ! = 0 and id_ptr . * ! = entity . raw ) {
if ( ! meta . isNull ( error_writer ) )
try std . fmt . format ( error_writer , " Trying to register lookup for entity '{}' as {d}, but it's already registered as {d} \n " , . { entity , entity . raw , id_ptr . * } ) ;
return error . LookupIdMismatch ;
}
id_ptr . * = entity . raw ;
return entity ;
} else {
if ( ! meta . isNull ( error_writer ) ) {
if ( parent ) | p |
try std . fmt . format ( error_writer , " Could not find child '{s}' of parent '{}' \n " , . { name , p } )
else
try std . fmt . format ( error_writer , " Could not find root entity '{s}' \n " , . { name } ) ;
}
return error . EntityNotFound ;
}
}
fn lookupAndRegisterDeclarations (
world : * World ,
parent : Entity ,
comptime T : type ,
error_writer : anytype ,
) ! void {
inline for ( @typeInfo ( T ) . Struct . decls ) | decl | {
const ChildType = @field ( T , decl . name ) ;
if ( @TypeOf ( ChildType ) ! = type ) continue ;
comptime var child_name = decl . name ;
if ( @hasDecl ( T , " _ " + + decl . name + + " _Name " ) )
child_name = @field ( T , " _ " + + decl . name + + " _Name " ) ;
const child = try lookupAndRegister ( world , parent , child_name , ChildType , error_writer ) ;
try lookupAndRegisterDeclarations ( world , child , ChildType , error_writer ) ;
}
}
} ;
}
test " Context multiple contexts " {
const flecszigble = @import ( " ./main.zig " ) ;
const expect = @import ( " ./test/expect.zig " ) ;
flecszigble . init ( std . testing . allocator ) ;
var world1 = try Context ( 1 ) . World . initMinimal ( ) ;
var world2 = try Context ( 2 ) . World . initMinimal ( ) ;
defer world1 . deinit ( ) ;
defer world2 . deinit ( ) ;
const Foo = struct { } ;
const Bar = struct { } ;
const foo1 = try world1 . tag ( Foo ) ;
const bar1 = try world1 . tag ( Bar ) ;
// Register tags in opposite order in `world2`.
const bar2 = try world2 . tag ( Bar ) ;
const foo2 = try world2 . tag ( Foo ) ;
try expect . equal ( foo1 , try world1 . lookupType ( Foo ) ) ;
try expect . equal ( bar1 , try world1 . lookupType ( Bar ) ) ;
try expect . equal ( foo2 , try world2 . lookupType ( Foo ) ) ;
try expect . equal ( bar2 , try world2 . lookupType ( Bar ) ) ;
// Due to registration order, and IDs being unique to each World,
// `Foo` from `world1` should have the same ID as `Bar` from `world2`.
try expect . equal ( foo1 . raw , bar2 . raw ) ;
try expect . equal ( bar1 . raw , foo2 . raw ) ;
}