#Modifying a struct

1 messages · Page 1 of 1 (latest)

wicked swift
#

Is there a way to modify structs at comptime? for example you pass a struct to a function and it adds a new field to it and either mutates the struct you passed in or returns the new struct if thats not possible

frosty dragon
#

you can't mutate a type

#

a type is an immutable value

#

you can create a new one based off of the original of course

#

see @Type

wicked swift
#

🙏 thanks

frosty dragon
#

also see @typeInfo for reflection of types

wicked swift
#

so what id do is id get the type info of the passed struct, edit it, and return @Type from that?

#

what im trying to do is make a gameobject sorta thing

#

so you can define your specific logic and have the framework add the necessary boilerplate fields and functions

#

but i also want the user to be able to access those automatically defined things in their code too

chilly vale
#

You can play with the fields but decls can't be reified with @Type

wicked swift
#

so theres no way to do what i want to do in a nice way?

chilly vale
#

well depends. Not in full generality like straight up code generation

#

which is unfortunate

wicked swift
#

would be nice if open ended types existed

#

as in

#

a type which you can add on to at compile time

chilly vale
#

that wouldn't make sense because of how the compiler works though. Types aren't vars.

#

But you can work around this kinda thing ususally

wicked swift
#

what do you suggest

chilly vale
#

if you have an idea of what kind of fields and decls need to be there then some combination of usingname and stuff like this can give you pretty decent semantics

#

like I'll need to see a concrete idea for what kinda types are you looking to generate first

wicked swift
#

simplified ecs, entities contain components, components contain data and logic

chilly vale
#

no like I mean specific examples

#

like can you give a brief example of what kind of structs you want to be able to mke

wicked swift
#

each component should have a destroy method provided by the framework (same between all components) and an entity field that points to the entity that owns the component

#

probably more eventually but these are the 2 basic ones

#

oh and also

#

the type itself should have a function for adding an instance of itself to an entity and a function to get itself from an entity

#

so essentially the whole package - methods, functions and fields

#

one idea i have now for methods and functions at least is:

#
pub const MyComponent = struct {
  const Base = lib.Base(MyComponent);
  usingnamespace Base;
}
#

lib.Base returns the functions/methods

#

for fields the user would just have to manually add them in themselves, i dont think theres a way around that?

chilly vale
#

not nessesarily, also have you explored stuff like @fieldParentPtr and various things like this? It can be used to make methods that upcast pointers to containing structs and stuff like this.

wicked swift
#

nope. ive been actively using zig for at most a week

#

i also find that the zig reference page is not that helpful, it contains the definitions but no examples of how to use the built-in functions

#

for most of them

chilly vale
#

its not great but its usuable. after a few weeks you would get the hang of it

#

okay I'll show you an example of this structfield that can find itself sort of thing.

#

one second

chilly vale
# wicked swift i also find that the zig reference page is not that helpful, it contains the def...

Okay here is an example of something you may find interesting:

const std = @import("std");

pub fn Component(comptime ParentContainer: type) type {
    return struct {
        pub const Self = @This();
        pub const Parent = ParentContainer;

        field1: usize = 0,
        field2: []u8 = "",

        const our_field_name: []const u8 = blk: {
            for (std.meta.fields(Parent)) |field| {
                if (field.type == Self) break :blk field.name;
            }
            @compileError("component does not exist in parent");
        };

        pub fn get_parent(self: anytype) switch (@TypeOf(self)) {
            *Self => *Parent,
            *const Self => *const Parent,
            else => @compileError("bad type: " ++ @typeName(@TypeOf(self))),
        } {
            return @fieldParentPtr(Parent, our_field_name, self);
        }
    };
}

const Entity = struct {
    const Self = @This();

    component1: Component(Self),
    field1: usize,
};

test "Going up!" {
    var my_entity: Entity = .{ .component1 = .{}, .field1 = 2 };
    const my_const_entity = my_entity;
    const entity_ptr = &my_entity.component1;
    const const_entity_ptr = &my_const_entity.component1;

    try std.testing.expectEqual(&my_entity, entity_ptr.get_parent());
    try std.testing.expectEqual(&my_entity, my_entity.component1.get_parent());
    try std.testing.expectEqual(&my_const_entity, const_entity_ptr.get_parent());
    try std.testing.expectEqual(&my_const_entity, my_const_entity.component1.get_parent());
}
wicked swift
#

doesnt look quite right

#

an entity is pretty much just an id, components are stored elsewhere, and components are what the user is meant to create

#

but i sorta get what you did there

#

you just have a sub struct with the boilerplate stuff

chilly vale
#

its just an example of how you can make these types that can do things like find the thing they are contained in

wicked swift
#

true

chilly vale
#

also if you know how it is implemented you could do all kinds of crazy polymorphic stuff in Vtables

wicked swift
#

not a fan of vtables

#

speaking of vtables actually

chilly vale
#

well VTables are how it would actually compile down to from something like C# right

wicked swift
#

ive seen how allocator interfaces work on the inside and they use vtables

#

that stuff is COMPLICATED andi dont understand why

#

they have like an iterator for the vtable and some other stuff

#

why would they have that, why not just have a struct with function pointers

#

its something i noticed in a lot of places but couldn't really wrap my head around

chilly vale
#

There is some github issues around the design decisions

#

something to do with llvm optimizations

#

allocator isn't too complicated

#

its a "fat pointer" which If i recall correctly is also what rust does

#

it contains just 2 pointers, a type erased one to its allocator instance and a pointer to a vtable with the allocating functions (alloc, resize, free)

wicked swift
#

the library ill be making is mostly polymorphism free so in that regard it should be fine. getting all components on an entity will be interesting though

chilly vale
#

polymorphism free? So you are making something with all compile time type safety?

wicked swift
#

if i thought of everything correctly the api is polymorphism free except for getting all components on the entity

#

doing everything else shouldnt require polymorphism

chilly vale
#

also if you want to explore more avenues of metaprogramming other than Vtables you could try doing some gnarly magic with passing around function pointers everywhere with comptime hooks.

#

Alternative, you could go full python style ducktyping.
Instead of writing methods, just pass around your structs to anytype functions.

wicked swift
#

mostly looking to make something simple to understand, nice to use and performant

chilly vale
#

you could make something simple to use if you are willing to be flexible with simple to understand

#

heavy metaprogramming just doesn't tend to be simple to understand.

wicked swift
#

yeah maybe not

#

im thinking though

#

whats the nicest way to allow for getting all components on the entity

#

all of them, even if they are of different types

#

maybe having an interface with all the functions and the component stuff as a struct inside the component

#

similar to what you showed with entities and components

tulip thorn
wicked swift
#

i tried most of the stuff previously mentioned and it was very ugly to work with sadly

tulip thorn
#

Heavy generics are generally not a good idea really, to be honest.

#

Things can get unweidly or awkward quite fast. 😄

wicked swift
#

weeelp these aren't really heavy generics

#

the goal is to have a library type that you can extend and thats it

#

sounds like inheritance but nnnope

#

either that or users are forced to put a lot of boilerplate in their structs

#

i dont think zig has anything to make this sort of thing nice yet

tulip thorn
wicked swift
#

i dont want to do it a specific way. its just the way ive done it before in other languages

tulip thorn
#

Right.

wicked swift
#

and im looking for an alternative that works in zig while still providing a nice api for the library user

tulip thorn
wicked swift
#

falling back is definitely not an option

#

imagine a game object structure sorta like unity

#

you have entities, and entities can have components that let u do logic

#

so components need to have a bunch of predefined fields and methods

#

that the user will also be interacting with

tulip thorn
#

A couple of other people have asked about how to do some ECS stuff in Zig.
Might be worth searching up on those; there's a couple of tricks that might be useful, like getting a unique ID per-type for example.

wicked swift
#

ive seen the source code for some ECS`s

#

the way they solve te problem is they make it so components are fully user defined

#

and dont need any library code