MrGVSV/bevy_proto

[Feature Request] Templates

Closed this issue · 5 comments

An excellent feature would be the ability to use some entities as 'templates':

---
name: "Template Enemy"
components:
  - type: Enemy

Then using the template would be like:

---
name: "Enemy 1"
template: "Template Enemy"
components:
  - type: Attack
    value:
      damage: 10
  - type: Armed
    value:
      weapons: [ "laser sword", "ray-blaster" ]
      primary: "laser sword"

The result would insert components from the template into the specific instance of it.

An excellent feature would be the ability to use some entities as 'templates':

That's a pretty good idea. It would help reduce some of the boilerplate that some entities may require. The main concern would be how to deal with inheritance (circular dependencies, overrides, etc.).

Possible Solution

Circular Dependencies

The first thing to do would probably be to do some dependency analysis when we first load the prototypes. That way we could warn the user if they are running into any potential issues, such as with circular dependencies, without needing to wait for them to actually spawn in the prototype.

Overrides

Next, to solve the issue of overriding, we could let Bevy handle it for us. If I'm not mistaken, they only allow one component per type on an entity, overriding previous versions of that component should a new one be inserted. When the spawn() method is called, we could just recursively call a spawn_internal() method that inserts the template components from the top of the hierarchy down to the base prototype.

So if the template defines an Enemy component, it will be used. But if the base prototype defines a different Enemy component, its version will be used.

Implementation

The real question is where do we implement this logic? Do we want this system to be embedded into the core Prototypical trait, or do we want this to be only available as an add-on (with the Prototype object or even a new TemplatePrototype object)?

Personally, I think a feature like this deserves to be embedded into the core logic. Most implementors of Prototypical probably want something like this, and if they don't they can just leave it out.

We'll have to be careful with how it's done, though. It will certainly require more trait methods be added, which could be a pain when implementing Prototypical. I think this could be doable if we hid most of the logic in default methods— assuming we can. And ideally, we'd want to keep the outward facing API small (0–4 parameters per method).


These are all just my thoughts on the subject and may or may not work out. Are there any suggestions for how this should be implemented? Or any other issues we might need to address?

I can try and get this started in a PR, and see how it works.

A simple way to solve the circular dependency problem would be to limit template recursion. In other words, templates shouldn't be able to have their own templates. That would be a little unfortunate though because it would add a lot of additional types or validations to deal with and limit flexibility.

Doing a dependency analysis during prototype load shouldn't be too hard since it would need to traverse the dependency graph in any case to load the data. It could simply keep a list of the prototypes it's already loaded for that entity and flag any duplicates.

I agree on having this as a part of the core logic. It certainly adds a bit of complexity, but it's an extremely powerful feature on which a lot of additional features could be built. I don't know too much about what's on your roadmap, but I have a hard time imagining what other core features one might need added here so I think whatever complexity this adds would hopefully be a one-off thing as opposed to a pattern for other features.

A simple way to solve the circular dependency problem would be to limit template recursion. In other words, templates shouldn't be able to have their own templates. That would be a little unfortunate though because it would add a lot of additional types or validations to deal with and limit flexibility.

I agree that this may be a bit too limiting. I imagine it could be pretty useful to define a base template which could be referenced by sub-templates (i.e. "Base Enemy" -> "Monster", "Alien", etc.)

Perhaps it would be best to just let the user deal with this issue. On bevy_proto's end, we can simply warn during the initial load, then short-circuit if an already-traversed template is found when actually applying them (probably with another warning), should they choose to ignore it.

I think, other than just panicking, this might be the simplest solution.

I don't know too much about what's on your roadmap

I don't have too much of a roadmap planned this early on. As I use it for my own projects or as others use it for theirs, it'll likely grow in some shape or form. Although, as Bevy's Scene System grows in maturity and receives better documentation, I imagine most people (including myself) might opt to use that instead. So it really depends on what's needed and what this crate might provide that Bevy doesn't.

I have a hard time imagining what other core features one might need added here so I think whatever complexity this adds would hopefully be a one-off thing as opposed to a pattern for other features.

Yeah, I think is a feature that most may expect to exist in some form or another for a crate such as this. As for other core features, I have some ideas, but again it depends on what users of the crate want and what features Bevy will add internally.

This looks great! This change makes this a really powerful plugin for bevy!

This looks great! This change makes this a really powerful plugin for bevy!

Thanks for the suggestion!