Code Monkey home page Code Monkey logo

Comments (34)

prime31 avatar prime31 commented on May 18, 2024 1

The more I think about this, the more I realize I should probably just fork and go totally custom. My needs are a bit different than the main repo's needs. I am building out a game engine which has very different needs than just a game. For example, the engine needs to manage a lot more with regard to resources, transform hierarchies and other ease-of-use items. A lot of plumbing needs to "just work" in an engine regardless of how the user does things which is a different from just a game consuming an ECS.

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

The current child/parent hierarchy is more oriented to handle scene composition and enable easy Dispose on a graph of Entity (nothing stop you to define multiple parents on a single Entity). In retrospection even the enumeration of children leave something to be desired since it's really just a HashSet but its exposition is mainly here for serialisation.

I could easily expose some kind of IsRoot property on Entity but I am not sure it feels right. If you need a fast way to get parentless Entity, you could always use a flag component to tag them as such when you create your scene and get them with an EntitySet.

// you would really need only one of them, Child is probably the better option since you don't need to know beforehand if the parent is a root or not
public readonly struct Parent {}
public readonly struct Child {}

...

Entity parent = world.CreateEntity();
parent.Set<Parent>();
...
Entity child = world.CreateEntity();
child.Set<Child>();
child.SetAsChildOf(parent);
...

// depending on which flag you prefer to use
EntitySet parentlessEntities = world.GetEntities().With<Parent>()...Build();
EntitySet parentlessEntities = world.GetEntities().Without<Child>()...Build();

Do you have just one level to propagate to or can you have multiple one?

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

It’s multiple levels deep. Basically just a standard scene graph. The problem is that only internal classes know about Entity removal which means you can get stuck with dangling parents if you try to do this from the outside.

Example:

entityParent = CreateEntity();
entityParent.Set<Transform>

entityChild = CreateEntity();
entiryChild.Set<Transform { Parent = entityParent }>
childEntity.SetAsChildOf(parentEntity);

parentEntity.Destroy()

// now child thinks it has a parent but doesn’t have a valid one anymore

Internally, World knows when an Entity is removed so it could handle cleanup but externally a System doesn’t know when an Entity is removed from its pool.

I have quite a few situations like this, where a System would like to know about new additions/removals.aybe an event for OnEntityAdded/OnEntityRemoved for an EntitySet might be a more flexible approach? This would make things like resource (audio, textures, shaders) management easier. A ResourceSystem would know when it lost an Entity and it could then clean up the resource handle. It would also know when it gained an Entity and it could load the resource.

Not sure what the best way to handle things is so I’m just tossing ideas out there ;)

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

On the resource dilema, this is a problem I will soon face in my game as I move from creating everything from code to using file to load my entities.

My idea is something like this:

// this is just a component with info on how to load the resource
public struct ResourceName
{
    public string Value;
}

[With(typeof(ResourceName))]
public class ResourceLoaderSystem
{
    private readonly Dictionary<string, Entity> loadedresources;

    protected override void Update(T whatever, ReadOnlySpan<Entity> entities)
    {
// copy entities because we will remove the component after setting the resource
// foreach
//// if resource is not in loadedResources
////// load resource
////// create an entity to act as the resource holder and add it to loadedResources
//// entity.SetSameAs<Resource>(loadedResourceEntity);
//// entity.SetAsChildOf(loadedResourceEntity);
//// entity.Remove<ResourceName>();
    }
}

Now all that is left to cleanup correctly is to check once in a while (probably overkill to do it every frame) if the entities in loadedResources have a child of not. If there's no child then no one is using the resource anymore.

This makes me thing it could be interesting to expose the number of reference on a component (shared with SetSameAs), that way you wouldn't even need to hook up entities as child/parent.

One other quick fix would be to call Dispose on component when removed if it implement IDisposable. Currently I don't do it because this could lead to unexpected behavior from users but by wrapping your resource into a IDisposable with a reference count, it could be handled quite nicely without any child/parent shenanigans.

I don't really know the right approch neither, I am no expert in ecs design, this framework is merely me trying to put things together after reading extensively on the subject and coming up with what I feel is a clean solution while making a game ^^.

Sorry I am on a phone right now so I hope it not too badly formated.

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

I found a bunch of good data with regard to dealing with a user-facing ECS by looking through the Unity Project Tiny code. They solved a lot of these problems already in there seeing as how Project Tiny is a pure ECS that is also entirely using-facing. It's definitely a good resource to mine to get some ideas ;)

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

I suppose I need to install Unity and the Tiny module to poke around its guts :o? Either my google-fu has failed me or then haven't released openly the code.

I think I'll definitely go with the IDisposable road and create a bunch of helper classes to handle the resource management problem. Something along those line:

// this is the component to hold information about the resource, how to load it, T being the type of the resource
public struct ResourceId<TInfo, T> where T : IDisposable
{
    public readonly TInfo Info;
}

// the class to do the actual loading
public class Loader<TInfo, T> : IDisposable where T : IDisposable
{
    // we can subscribe on ComponentAddedMessage<ResourceId<TInfo, T>> to call the load function (if the resource is not yet loaded of course) and set T on the entity
    // but also a Resource<T> which hold the reference count on T, when that component or T is removed we will call its Dispose to check if we need to really dispose T automatically
    public Loader(World world, Func<TInfo, T> loadFunction);

    // dispose all currently loaded resources
    public void Dispose();
}

What do you think, is it too cumbersome to use or not enough permissive by hiding too much? I haven't decided if it's worth to expose the Resource type.

It’s multiple levels deep. Basically just a standard scene graph. The problem is that only internal classes know about Entity removal which means you can get stuck with dangling parents if you try to do this from the outside.

Example:

entityParent = CreateEntity();
entityParent.Set<Transform>

entityChild = CreateEntity();
entiryChild.Set<Transform { Parent = entityParent }>
childEntity.SetAsChildOf(parentEntity);

parentEntity.Destroy()

// now child thinks it has a parent but doesn’t have a valid one anymore

Actually when calling parentEntity.Dispose (I guess this is what you meant with Destroy) the child will also be disposed so there won't be an invalid entity there.
The real problem is how to handle propagation of value through entities. I also need this to handle multi part animation relative to their parent but I have yet to find a clever idea on this.

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

The Unity Project Tiny stuff is hard to find the details of. It currently only allows TypeScript as the language so the best source of data is the TypeScript bindings file:

ProjectTiny.zip

Lots of stuff in there isnt relevant since they have a goal of being, well, tiny. They give up some functionality and make other stuff more difficult (transform scale/pos/rot for example) to do that but it's still a good information source to see how they did things.

Your resource loader could work. It doesn't seem too cumbersome. Project Tiny has an interesting way of handling this that I might implement as a test:

  • when Destroy/Dispose is called on an Entity it checks to see if it has any Components that have the ISystemStateComponent interface
  • if one is found, all Components are removed from the Entity except ISystemStateComponents
  • a CleanupEntity Component is added letting the Systems know that the Entity has some resources/state that need to be cleaned up

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

Wou nice thank you, I'll try to dissect it :)

Hm that's an interesting take indeed, I'am not a fan of keeping the entity alive when you explicitly want to destroy it but it is a nice way to leave the cleanup to the user when he want/can actually do it.

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

So I tried to go with my idea of resource managed and I'm quite happy with the result. It works across multiple World instances the user code is minimal.

    public sealed class TextureResourceManager : AResourceManager<string, Texture2D>
    {
        private readonly GraphicsDevice _device;
        private readonly ITextureLoader _loader;

        public TextureResourceManager(GraphicsDevice device, ITextureLoader loader)
        {
            _device = device;
            _loader = loader;
        }

        protected override Texture2D Load(string info) => _loader.Load(_device, info);

        protected override void OnResourceLoaded(in Entity entity, string info, Texture2D resource)
        {
            entity.Get<DrawInfo>().Texture = resource;
        }
    }

// this is how to set up a resource manager on a world
textureResourceManager.Manage(_world);

// and this is how to trigger the load
entity.Set(new ManagedResource<string, Texture2D>("square.png"));

Now onto the hierarchy propagation probleme >

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

Interesting approach. So you are using ComponentAdded/RemovedMessage to manage your reference count and handle disposing. Are you making ResourceManager subclasses for all of your different resources or are you going to combine them into a single manager? It also looks like your ResourceManager need to know about some other Component (DrawInfo). What if an Entity had a different Component that it wanted the resource loaded into?

I've got a different approach that I'm trying out (kind of like Project Tiny is doing under the hood). A System looks for any components that need resources loaded (textures, audio, tilemaps, etc). Once it loads the resource it puts a marker Component on the Entity so that it doesn't process it any further.

An example is AudioSystem. It handles loading, starting and stopping sound effects. It looks for GetEntities().WithAny<AudioSource, AudioClipLoadFromFile, AudioSourceStart, AudioSourceStop>().

  • AudioSource hold the loaded audio file.
  • AudioClipLoadFromFile has the path to the audio file (this will be hidden in the visual editor later)
  • AudioSourceStart tells the AudioSystem to start/resume playback
  • AudioSourceStop tells the AudioSystem to stop playback

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

At first I wanted to use the ComponentAdded/RemovedMessage but there would have been a problem if someone disable/enable the entity or the component itself (then the messages would not be sent so it would have been more complicated to process the resource).
I added two new messages ManagedResourceRequestMessage<T> and ManagedResourceReleaseMessage<T> to handle this. Even if an Entity is disabled, it seemed more logical to me to still load its resources so everything is correctly set when re-enabled.
Each type of resource needs its own manager class, on the base class AResourceManager<TInfo, TResource> TInfo is what act as key and needed info to load the resource only once (in my exemple the name of the texture) and TResource is the type of resource obviously.
If a single entity needs multiple resources of the same type, it also support ManagedResource<TInfo[], TResource> and the OnResourceLoaded will be called for each loaded resource so you can do what you need.

public enum SoundKind
{
    DeathEffect,
    HitEffect,
    ...
}

// this is the TInfo
public struct SoundEffect
{
    public SoundKind Kind;
    public string Name;

    // since this type will act as key, but we don't want to duplicate the same sound which could be used by multiple kinds
    public override Equals(object other) => other is SoundEffect se ? Name == se.Name : false;
}

// this would be the inplementation of the method on a AResourceManager<SoundEffect, Sound> derived class
protected override void OnResourceLoaded(in Entity entity, SoundEffect info, Sound resource)
{
    switch (info.Kind)
    {
        case SoundKind.DeathEffect:
            // set up the sound on a component somewhere
            break;
        ...
    }
}

entity.Set(new ManagedResource<SoundEffect[], Sound>(new []
{
    new SoundEffect("deathsound", SoundKind.DeathEffect),
    new SoundEffect("hitsound", SoundKind.HitEffect)
});

This way the user can do whatever he wants with the resource, it is not set as a new component on the entity by default.

Your approach is good I think, this is what I wanted to do at first with no specific code to handle it in the framework. But as we talked I thought it would be better to give something more optimized and integrated with less boilerplate, sharing resources across multiple worlds (I create a new world for each scene in my game).

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

My plan is always to try to use a framework without modifying it, which is what I was trying to do when I opened this issue.

For some reason, I am not capable of using any code without changing it though! Just today, I started using TiledSharp. 10 minutes after starting to use it I ended up changing it. A couple hours later and now only the class names are the same. I even forked my own game engine to start this new one.

Anyway, seeing as how you are adding it to the framework, I will give your way a shot. I am going to try and see if I can continue to use the framework without forking it. It would make things easier down the road for me if I can just use it as it is ;)

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

Slightly off topic, but I just ran into this when trying to make an in-game Entity inspector and some other stuff.

How do you clone an Entity and all of its Components? I can't seem to find anything in the public API that does this unless I am missing something.

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

How do you clone an Entity and all of its Components? I can't seem to find anything in the public API that does this unless I am missing something.

There actually is a method for that but I have not documented it yet in the readme as I wasn't sure of this feature: Entity.CopyTo(World)

For some reason, I am not capable of using any code without changing it though! Just today, I started using TiledSharp. 10 minutes after starting to use it I ended up changing it. A couple hours later and now only the class names are the same. I even forked my own game engine to start this new one.

Anyway, seeing as how you are adding it to the framework, I will give your way a shot. I am going to try and see if I can continue to use the framework without forking it. It would make things easier down the road for me if I can just use it as it is ;)

Haha you and me both, I manage to stop the "reinvent the wheel" mindset at work but home? anything goes! If something does not suit my style no problem I'll just do it, that's how this framework was born in a way (and toying with new c# feature).
I couldn't help but notice that you are the one behind nez?! I haven't used it myself but I've read good stuff on it, pretty impressive you are way more knowledgeable and experienced than me on this subject :o

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

I saw that Entity.CopyTo method but didnt actually check the source code on it. I incorrectly thought it was only to copy an Entity to a different world. Works like a champ. Maybe add an overload that uses Entity.WorldId to make it more clear?

public Entity CopyTo(World world = null)

The code doesnt seem to work when passing in a World anyway ;) Notice that WorldId is used. world.WorldId isnt used.

        public Entity CopyTo(World world)
        {
            if (WorldId == 0) Throw("Entity was not created from a World");

            Entity copy = world.CreateEntity();
            try
            {
                Publisher.Publish(WorldId, new EntityCopyMessage(EntityId, copy));
            }
            catch
            {
                copy.Dispose();

                throw;
            }

            return copy;
        }

I couldn't help but notice that you are the one behind nez?!

Nez is my little baby. I am actually working on Nez 2 right now. Nez 1 used an EC system as the base and then tacked on an optional ECS. It was basically a silly hybrid setup. Not a great design but I wasnt using the ECS so I didnt mind.

Nez 2 is a stripped down version (not much at all was kept in the fork). It's going pure ECS with no EC abilities at all. Nez 2 is also getting built with Scene serialization in mind to make life easier than trying to tack it on later.

Screen Shot 2019-03-17 at 10 02 44 AM

One more little thing:

  • it would be handy if Entity.ToString printed out EntityId and WorldId for display in an editor. I'm even thinking about adding Entity.Name and a way to lookup an Entity by its name. That would make display more clear and help out in situations where you have an Entity you want to look up just once rather than creating an EntitySet to do it. It could be something stored alongside the Entity in a parallel array rather than with it to avoid crufting up the Entity.

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

Actually it does work on a different world, world.WorldId is contained in copy, the new entity which will receive all the component of the current instance. If you look in ComponentPool code:

        private void On(in EntityCopyMessage message)
        {
            if (Has(message.EntityId))
            {
                message.Copy.Set(Get(message.EntityId));
            }
        }

This is old code and doesn't copy exactly the entity now though since I added way to disable/enable entities and components, this state is not carried over, I should probably do something about it!

Nice work on Nez2, to think this is something I want to do at some point, an editor for my framework which can also be plugged on running games to see entities information in real time, now I know it's possible! (and maybe not needed anymore ^^)

On Entity.ToString, I only added [DebuggerDisplay("Entity {WorldId}:{EntityId}")] to help me identify entities when debugging since I don't have an editor yet. I thought about giving a string information so you can be more explicit about what is what, it could even be used as the Entity name when serializing it. It could be a new string field on the EntityInfo struct but you could just as easily set a string component (or custom internal type, so your user can't override it by accident by also setting the string component) on the entity to name it.
I am still not too happy about how I handle child/parent with the HashSet which is not reused when recycling entities so I'm not fond of adding an other ref "attribute" on EntityInfo.

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

Actually it does work on a different world, world.WorldId is contained in copy

lol. I need to get some more sleep. I'm blind today! Duh.

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

So, about that whole parent/child situation. It would be really nice to have something like this on Entity at the very least:

public bool HasParent() => World.Infos[WorldId].EntityInfos[EntityId].Parent != null;

This is the code I just wrote to determine if an Entity has a parent (the equivalent to that nice, fast one liner above with reflection):

var worldId = (int)ReflectionUtils.GetFieldValue(entity, "WorldId");
var entityId = (int)ReflectionUtils.GetFieldValue(entity, "EntityId");

var infosField = typeof(World).GetField("Infos", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
var infosArray = (object[])infosField.GetValue(null);
var worldInfo = infosArray[worldId];

var entityInfoArray = (Array)ReflectionUtils.GetFieldValue(worldInfo, "EntityInfos");
var entityInfo = entityInfoArray.GetValue(entityId);
var hasParent = ReflectionUtils.GetFieldValue(entityInfo, "Parents") != null;

I feel dirty after writing this. Time to take a long shower ;)

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

lol I'm so sorry you had to write that x), this is exactly what I had in mind with my IsRoot suggestion at the beginning of this conversation.
I still believe the parent/child should only be for dispose relationship and component propagation should be handled with something else but if I don't come up with some other solution I'll add it.

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

You're probably right. I should just bite the bullet and move my scene graph relationship over to my Transform Component system. I am trying to avoid that so that folks using the code don't get confused by two parenting systems: Entity and Transform. Hierarchies would be confusing to people when they can do Entity.SetParent and Transform.SetParent when only one of those calls actually creates the spatial representation they are looking for.

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

Yes I probably didn't think all that through when adding those methods >_< Maybe I should rename them to avoid this confusion if a better solution is found.

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

So, I started checking out the new resource additions. Unless I am missing something (very possibly true) I think resources are getting double disposed. The flow seems to be going like this:

  • the last Entity with a resource gets Disposed
  • ComponentPool.Remove is called and publishes a ManagedResourceReleaseMessage
  • the message is received here
  • resource.RemoveMessage is called which disposes of the resource if the ref count reaches 0 here
  • resource.RemoveMessage returns true because ref count is 0 and the resource gets disposed again here

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

No you are right, this is a bug. At first I wanted the wrapper type to dispose the resource on its own but changed my mind to use it only as a reference counter and really dispose the resource outside in the resource manager. I completely forgot to remove the first dispose call, thanks for proof reading it!

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

So on the entity relationship, I read some stuff on the subject to get some inspiration and ended up on this article.
To reuse the transform example, I think this would be a solution:

  • a LocalTransform component
  • a FinalTransform component
  • some kind of ParentReference component to point on a parent entity
  • a system to process LocalTransform, what you should have originally
  • a system to calculate FinalTransform, if the entity does not have a ParentReference, just copy it's LocalTransform into FinalTransform, else loop through each successive parent entity to get its LocalTransform and apply

pro:

  • the code is simple
  • no limit in hierarchy level
  • can be paralleled like any other system

con:

  • need to create this ParentReference which should remove itself someway if the parent is disposed or lose its LocalTransform component
  • the deeper the level, the higher the cost, jumping across memory for each parent

I think most entities would be at root level so the cost would not be that big.

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

That is similar to what I have setup. This is what I ended up with:

  • Transform component houses position, rotation, scale
  • TransformMatrixes component added at runtime houses objectToWorld and worldToObject matrixes
  • TransformStatic component indicates static geometry so transform is only calculated once
  • TransformFrozen component added at runtime to indicate the transform matrixes were calculated
  • TransformSystem runs before rendering and does the following:
    • loops through all Entities with Transform:
      • if Entity has parent skip and continue loop
      • if TransformFrozen component exists skip and continue loop
      • [START] add TransformMatrixes component if it isnt there
      • calculate transforms and populate TransformMatrixes component
      • if TransformStatic exists, add TransformFrozen component
      • (back to [START]) iterate children recursively and calculate their transforms

The only bit I have to redo in here is to not use the Default ECS parent/child system and instead add a TransformNode which houses parents and children.

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

Seeing as how this issue has become a discussion grounds, I have a design question for you. How did you end up designing your render system?

I am currently in limbo on this one. I have something that handles sprites well. It has sorting rules like this:

  • LayerSorting layer (LayerSorting is an optional component. When not present it defaults to all zeros)
  • LayerSorting orderInLayer
  • CameraAxisSort x/y (CameraAxisSort is an optional component that lets you specify x/y as -1, 0, 1 which makes sorts happen top/down, bottom/up, left/right, right/left and diagonal based on the Entity x/y positions)
  • finally, if we still have a tie layerDepth is used (z-value of transform)

The RenderSystem basically does this so far:

  • for each Camera
    • loop through all Entities with 'Sprite' and Transform component
      • ensure Sprite is on a render layer the Camera renders
      • cull the Sprite based on Camera frustum
      • if it passes, add it to the Entity render list
    • sort Entity render list
    • draw all Sprites (this works for basic and tiled Sprites with or without custom Materials)

This system is fine for very basic stuff but is not very configurable besides sorting. The things I need to add:

  • ability to render non-sprites (particles effects, tilemaps, custom meshes)
  • some way to add post process effects
  • optionally render Cameras to a RenderTexture (or even split up Camera rendering into two or more passes for more control)
  • need some way for a user to do something with the RenderTexture if a Camera renders to one
  • all of that requires a default RenderTexture that is used for all rendering. But where does it live and how does someone access it to write to it?
  • some system somewhere needs to handle writing the final RenderTexture to the back buffer, either directly or piping it through ImGui for the editor features.

So, after that pile of text, have you solved any of these issues yet? If so, how did you do it?

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

child/parent stuff: I think there are situations where you want to create dependency relationships between entities which are different depending of the component. We have the obvious transform one but someone may need a completely different child/parent graph for an other feature at the same time so relying on the dispose chain seems too limiting.
The only thing missing in the framework then I think is this RelationShip component, which should be able to clean itself if the parent disappear, maybe even change ownership to the parent's parent automatically.

render system: keep in mind that I do not have much game development under my belt so I may be completely wrong in the way I do things. As I am making a simple game instead of a full blown editor, obviously I can code and rearrange systems as needed.
Currently I only have a simple bloom effect. the class wrapping the effect has BeginCapture which change the render target, EndCapture which do what is need on its render target and set it back to the previous value, and Draw which finally output its work.
This is part of my draw system initialization:

                new ActionSystem<float>(_ => _bloomEffect.BeginCapture()),
                new DrawSystem<Bloom>(_world, game.GraphicsDevice),
                new ActionSystem<float>(_ => _bloomEffect.EndCapture()),

                new DrawSystem<LayerBackground>(_world, game.GraphicsDevice),
                new DrawSystem<LayerUnit>(_world, game.GraphicsDevice),
                new DrawSystem<LayerParticle>(_world, game.GraphicsDevice),
                new ActionSystem<float>(_ => _bloomEffect.Draw()),
                new DrawSystem<LayerUi>(_world, game.GraphicsDevice)

DrawSystem is generic so I can reuse the same code for different layer, a component type acting as a tag, or for the bloom effect. So the render target lives in the bloomEffect object, which is not handled by ecs.
Particles are essentially the same as any other object (I have yet to look into gpu particles), just I'm using a pool to quickly get disabled entities with all the expected component already in place to speed up the process and enable them as needed.
In a previous prototype in 3d (the one where I started working on DefaultEcs actually, switching from a traditional OoP), meshes were not different than sprite. Instead of using a sprite batch, each entity had a Drawer component, housing the model inside. Calling draw would add a DrawEntry on a circular buffer, basically the projection matrix, and in the end I would call present all drawer, to reduce the gpu draw calls.

from defaultecs.

mjr27 avatar mjr27 commented on May 18, 2024

Let me drop my 2¢.

Recently i was dealing with the same thought -- I loved how Nez deals with transformation graph, and tried to make the same using DefaultEcs. However, for obvious reasons, it failed.

On my second approach I understood that this was originally bad idea. DefaultECS provides parent/children relations for lifetime events (basically, chain dispose). However, it doesn't always correspond to the rendering graph -- a spawned enemy skeleton is an obvious example, it's a logical child of the summoner, but a visual child of the space. So it's much better to implement as a separate component.

Bottom line, current implementation is perfect for its purpose (chain disposal) and doesn't prevent implementing anything else on top of that. Accessing parent may find some uses, but unless it's completely free in terms of performance/memory consumption, it isn't worth it.

P.S: thanks both of you for your amazing work, @Doraku, and @prime31. Coming from webdev, i felt a bit depressed with lack of literally TONS of well documented opensource libraries and tools i used to have access to. 🤣 Until i found both of your libraries

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

I've got a possible request for you (or, actually three of them), feel free to say it is a dumb idea if you don't like the it ;)

  1. It would be handy for Entity to override ToString with a string that prints out WorldId and EntityId
  2. Having WorldId and EntityId be public would also be really nice. That way if ever an Entity needs to be used as a lookup table (such as a key in a dictionary or hash map) we could just use Entity.EntityId instead of the entire struct. This saves having to make a custom IEqualityComparer to avoid boxing the keys.
  3. As a really nice bonus, it would be handy if Entity had a DebuggerTypeProxy that fetched all the Components and stuck them in an object[] so they can be inspected in the debugger.

If you like any of these changes I can PR any of them ;)

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024
  1. It would be handy for Entity to override ToString with a string that prints out WorldId and EntityId

Hum I'm not completely against it, those info are already available as a debug display and ToString is mostly used to debug but not entirely which goes with my problem on your second point >

  1. Having WorldId and EntityId be public would also be really nice. That way if ever an Entity needs to be used as a lookup table (such as a key in a dictionary or hash map) we could just use Entity.EntityId instead of the entire struct. This saves having to make a custom IEqualityComparer to avoid boxing the keys.

This expose the inner working and I can't shake the feeling that it's leaking too much in the api. Also Entity already has an override for GetHashCode tailored for hash map containing entities of a single World and implement IEquatable<Entity> to remove boxing, you should not have to provide a custom IEqualityComparer to optimise that (or I have a really wrong understanding of how it is working)

  1. As a really nice bonus, it would be handy if Entity had a DebuggerTypeProxy that fetched all the Components and stuck them in an object[] so they can be inspected in the debugger.

Now that I would take! Never used this attribute before, it is a great idea. Although I'm not sure about exposing them as object[], you would lose the real type of the component used (nothing stop you to set interface as component). Maybe a Dictionary<Type, object>?

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

I figured 2 would be a nogo but figured I’d toss it there anyway ;)

For 3, it doesn’t much matter what the actual data format is that you stick the objects in. It only gets used by the debugger when inspecting an Entity so it can be crazy inefficient. All my debugger proxies are huge hacks that just expose data any way they can.

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

The Entity is the key itself, have to keep the magic hidden :p

Yeah this is just for the exactitude of what is displayed in the debugger, so that if you do entity.Set<object>(42) you see that you indeed have a component of type object and not int.

from defaultecs.

Doraku avatar Doraku commented on May 18, 2024

I believe this issue has gone well beyond its original scope.
The problem of hierarchical component relation remains but as discussed I believe using the dispose chain hierarchy is not a good solution. A custom component type to handle the link is preferable but I'm not sure is this has its place in the base framework.
If I (or anyone!) end up founding a good solution it will make its way here but for now I am closing the issue :)

Btw I ended up putting DebuggerTypeProxy for all the important types I could think of. How did I live as a .Net developer for all those years without this?!

from defaultecs.

prime31 avatar prime31 commented on May 18, 2024

Yeah, just slightly beyond its original scope ;)

Btw I ended up putting DebuggerTypeProxy for all the important types I could think of. How did I live as a .Net developer for all those years without this?!

See that? Sometimes its a good thing when something misses the scope and ends up on the back roads. DebuggerTypeProxy gets even more useful when you are messing with unmanaged code. Rather than a useless pointer address you can reformat the data as you see fit. Super handy.

from defaultecs.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.