SanderMertens / flecs

A fast entity component system (ECS) for C & C++
https://www.flecs.dev
Other
6.44k stars 451 forks source link

Add a proper data validation and hints API to the meta addon #1085

Closed mctb32 closed 11 months ago

mctb32 commented 11 months ago

After looking at the features of the meta addon I would like to make a feature request for adding a proper data validation and hints api. I believe it would be necessary to support a serious authoring tool.

The Problem

Currently, the only validation data that can be present in the metadata are the three ranges for numerical members (range, warning_range, error_range). The ranges make sense only for numerical values, while other members such as strings, vectors or entities cannot be validated in any way. A vector member could provide a max length, a string could validate against a charset, etc. The ability to constraint an entity handle would be the most interesting – for example by making it accept only entities that match a filter. Without it, an editor application cannot give any hints to the user who is editing an entity handle inside a component. For example, setting an entity handle inside a MeshInstance component should show a list of all entities that contain a MeshData component, not a list of all entities in the world.

Second, the current validation is not only limited but also is static. In a real world application the ranges for numericals (and any other members) might depend on the world state. For example a point2d might represent coordinates on the current map, so the possible range might depend on the current map size.

Proposed solution

To support this I propose to remove the 3 ranges and the unit fields from ecs_member_t, and replace them with two function pointers:

Both functions would also take the entity and component ids, so that the validation and hint results could be truly dynamic. Changes to existing code should be minimal as only affected parts would be the existing ranges and unit registration and reading. Of course, the usage of the validation api and places where it would be enforced would be totally up to the user. To make everything more accessible I would propose to add some helper functions like ecs_validate_entity, ecs_validate_entities, ecs_validate_set, or ecs_init_entities_to_defaults. Perhaps even add a way to enable/disable validation for all set functions in a given world.

SanderMertens commented 11 months ago

Component validation is something that's on the roadmap, though the mechanism will be a bit different. Flecs already has "hooks", which are functions that are executed when component lifecycle events happen (like on_add, on_set, on_remove etc. see https://github.com/SanderMertens/flecs/tree/master/examples/cpp/entities/hooks). The current plan is to integrate validation with the on_set hook, and marking the component as invalid when validation fails.

Ranges are a special case of validating component that's very fast to evaluate and works well in applications that need to check large numbers of components (one place where this happens is in the alert addon). Another advantage of ranges is that they can be serialized, which means that tooling like https://www.flecs.dev/explorer/ can visualize them (e.g. by changing the color of a value to red if it's out of range).

To support this I propose to remove [...] the unit fields

I'm not sure why you're proposing to remove unit, that's orthogonal to validation.

mctb32 commented 11 months ago

Good to know that validation is on the roadmap. I’m sure hooks can be used to implement it, but isn’t this a part of public API and you can register only one hook per component per action? So any client code that is currently using hooks would become incompatible with the validation feature.

Also, I’m not sure how such a setup could support all of the scenarios I described. The client needs to be able to switch the validation on and off. In a typical setup you want the validation on during data authoring in the editor, set to only log errors/warnings in debug builds of the game, and disabled in release to conserve performance. Also you might want to do single data validation passes on the whole world at certain points, for example after loading in a saved game.

But finding a place to store and run the validation code is only part of the needed feature. Equally, or even more important is the ability for the app to reflect on the validation rules to the authoring tools. The editor needs to display hints to the users and create a safe environment so that it is hard to mess things up. The actual validation rules could be of any desired complexity as they are just client code, and they can run on the whole component like you proposed, but the reflection part just needs to report hints about the expected data usage that the editor can actually understand and enforce. Hints like min-max range, a max length or charset for a string, a filter for an entity handle, etc. But it doesn’t have to stop there, the hints could also include things other than the description of validation rules. This would be a good place to report default values, tooltips, desired widgets to edit the value, disabled or hidden states, etc. A Unit could also go here, and this is why I suggested to remove it in favor of an API that could describe members in a much more refined and extendible way.

So I guess I’m asking for two features, and this probably caused confusion. First, a validation API can be added to the hooks structure, but I would suggest adding another function pointer, instead of using the on_add, on_set, on_remove which can already be used for other purposes by users. Second, a hints API to query for arbitrary properties of component members (replacing ranges and unit stored in the member desc currently).

SanderMertens commented 11 months ago

Second, a hints API to query for arbitrary properties of component members (replacing ranges and unit stored in the member desc currently).

Can you provide a code snippet of what you're thinking about? I think the current design already does this, the REST API (and JSON serializer) allow you to request the schema of a type, which includes member constraints. Right now that's limited to only a numeric range, but you could for example extend this with a regex field.

mctb32 commented 11 months ago

Ok, how can I extend the schema of a type in a component then? The numerical ranges and Unit are this sort of meta-data, but they are hardcoded in ecs_member_t.

Let’s say I’m an author of a flecs module, and I’m registering a Viewport component with a vec4 member. I want to be able to attach arbitrary data to this member that might be of interest to the editor (for example “widget=colorselect_hdr; tooltip=Viewport background clear color; default_val={0,0,0,1}”). I don’t see a way of doing that right now, without modifying the structures or writing a parallel reflection system.

Also, I don’t want to force the user of my module to have to use the REST addon. It should be possible to implement a built-in editor that manipulates the world directly instead of going through the REST api. I can read the hardcoded ranges and Unit currently without going through the REST api, and with user fields it should be the same.

Not to mention that storing a hardcoded Unit and 3 ranges that make sense only for numerical members inside each member in an API that is type agnostic just doesn’t look right to me.

I guess that I could store the hint data in a separate component next to .meta component, and look the data up when the editor is iterating over the members and building widgets, but this would be basically building another reflection layer on the side. It would be cumbersome to use and error prone as both reflection layers would have to be kept in sync (either matching member ids or matching names) and part of the data (ranges and unit) are already hardcoded inside flecs.

SanderMertens commented 11 months ago

Ok, how can I extend the schema of a type in a component then?

Members are represented by entities in Flecs, which means you can add your own components to them (the reflection framework does this also). For example:

entity p = world.component<Position>()
  .member<float>("x")
  .member<float>("y");

p.lookup("x").set<MyComponent>({ ... });

Also, I don’t want to force the user of my module to have to use the REST addon.

You don't have to, I just used the JSON serializer/REST addon as example as they show this is already possible with the existing reflection framework.

that make sense only for numerical members inside each member in an API that is type agnostic just doesn’t look right

Agree to disagree :) I understand the point you're making, but IMO the benefits (easy to implement & understand, fast to evaluate) outweigh the esthetic disadvantage- especially since it doesn't prevent or make it harder to add more sophisticated checks.

mctb32 commented 11 months ago

Members are represented by entities in Flecs, ...

Obviously I didn’t know that… I was about to ask if you considered making members entities. On my defense I must have missed this when I was reading the manuals, and when I was looking at flecs playground, none of the components were showing any member children. Are they hidden from the explorer or are they not related with ChildOf to the component?

Zrzut ekranu 2023-12-03 112900

Ok, so I see that the ecs_member_t is only used to automate member registration, and the actual storage is done via components such as EcsMember or EcsMemberRanges. So this is great, I can indeed extend members with arbitrary data the editor will be able to use. In my case this will be a callback function, because I need my hints to be dynamic (returning different results depending on the particular entity state, or even the world state).

Since you hardcoded the ranges and unit in the registration helpers api I’m guessing there is no way to provide custom components for the members during component registration? I have to register a struct using ecs_struct(…) and later look up the member entities to add custom components?

SanderMertens commented 11 months ago

Are they hidden from the explorer or are they not related with ChildOf to the component?

If a component has registered members, they'll show up in the explorer:

Screenshot 2023-12-03 at 11 44 36 AM

I’m guessing there is no way to provide custom components for the members during component registration?

You can also set the Member component (and others) directly, for example:

flecs::entity pos = world.component<Position>().scope[&]{
  world.entity("x").set<flecs::Member>({ world.id<float>() });
  world.entity("y").set<flecs::Member>({ world.id<float>() });
});
mctb32 commented 11 months ago

Thanks for explaining everything and for your patience. I think the issue can be closed.

I still think it would be handy if flecs had built in components and systems for validation and more editor hints (preferably dynamic in both cases so that validation rules and hints could depend on entity/world state) and some guidelines on how to use them to promote standards among flecs module developers. But such features can be built on top of the existing API and since everyone probably has different needs it probably shouldn’t be at the top of the priorities list.