Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relationships added to ComponentTypes causes difficulties creating a cloning system #74

Open
ClxS opened this issue Sep 11, 2024 · 0 comments
Labels
enhancement New feature or request

Comments

@ClxS
Copy link

ClxS commented Sep 11, 2024

Mentioned this on the Discord a couple times. In my game I have the concept of multiple separated worlds being active and simulated at once, with entities transitioning from one world to another at certain transition points. I also use Relationships for various things (parent-child relationships, material instancing for draw batching, particle emitter batching)

When an entity crosses a transition point, I "move" it by duplicating the entity and erasing the old one. This is where problems come in - GetComponentTypes() also returns the internal Relationship tracking components. You need to use reflection to filter out the relationship components, or you will likely get an AccessViolationException due to the invalid setup post-cloning.

Below is my CloneEntityRecursive method which does this step.

private static Entity CloneEntityRecursive(Entity sourceEntity, World targetWorld, Dictionary<Entity,Entity> oldToNewEntityMap, List<Entity> scripts)
{
    if (oldToNewEntityMap.TryGetValue(sourceEntity, out Entity existingEntity))
    {
        return existingEntity;
    }
    
    ComponentType[] sourceEntityTypes = sourceEntity.GetComponentTypes().Where(t => !t.Type.Name.Contains("Relationship")).ToArray();
    Entity newSourceEntity = targetWorld.Create(sourceEntityTypes);
    foreach (ComponentType type in sourceEntityTypes)
    {
        if (sourceEntity.Get(type) is { } value)
        {
            newSourceEntity.Set(value);
        }
    }

    if (sourceEntity.HasRelationship<TransformChild>())
    {
        foreach(KeyValuePair<Entity, TransformChild> child in sourceEntity.GetRelationships<TransformChild>())
        {
            Entity newChild = CloneEntityRecursive(child.Key, targetWorld, oldToNewEntityMap, scripts);
            newChild.SetTransformParent(newSourceEntity);
        }
    }

    if (sourceEntity.HasRelationship<LogicalChild>())
    {
        foreach(KeyValuePair<Entity, LogicalChild> child in sourceEntity.GetRelationships<LogicalChild>())
        {
            Entity newChild = CloneEntityRecursive(child.Key, targetWorld, oldToNewEntityMap, scripts);
            newChild.SetTransformParent(newSourceEntity, inheritTransforms: false);
        }
    }

    if (sourceEntity.HasRelationship<EntityScript>())
    {
        foreach(KeyValuePair<Entity, EntityScript> script in sourceEntity.GetRelationships<EntityScript>())
        {
            Entity newScriptEntity = CloneEntityRecursive(script.Key, targetWorld, oldToNewEntityMap, scripts);
            newScriptEntity.AddRelationship(newSourceEntity, new ScriptAttachedEntity());
            newSourceEntity.AddRelationship(newScriptEntity, new EntityScript(script.Value.Script));
            
            Log.Info($"Found script {script.Value.Script.AssetUri.Uri}. Entity {script.Key.Id}");
            scripts.Add(newScriptEntity);
        }
    }
    
    oldToNewEntityMap[sourceEntity] = newSourceEntity;
    return newSourceEntity;
}

and SyncEntities, where I support unpacking one World into another

private void SyncEntities(World world, Span<Entity> entities, List<EntityGuidLookup> newEntityGuidLookups,
    List<EntityScriptGuidLookup> newEntityScriptGuidLookups, Dictionary<Entity, Entity> newEntityToLevelEntity)
{
    foreach (Entity entity in entities)
    {
        var components = entity.GetComponentTypes().Where(c => c.Type!.GetInterface("IRelationship") is null)
            .ToArray();
        
        Entity newEntity = world.Create(components);
        foreach (object? component in entity.GetAllComponents())
        {
            if (component is null)
            {
                Log.Error("Component is null");
                continue;
            }
            
            if (component.GetType().GetInterface("IRelationship") is not null)
            {
                continue;
            }
            
            newEntity.Set(component);
            
            if (this.entityGuidLookup.TryGetValue((entity, component.GetType()), out EntityGuidLookup lookup))
            {
                newEntityGuidLookups.Add(lookup with { Entity = newEntity });
            }
        }
        
        if (this.entityScriptGuidLookup.TryGetValue(entity, out EntityScriptGuidLookup scriptLookup))
        {
            newEntityScriptGuidLookups.Add(scriptLookup with { Entity = newEntity });
        }
        
        newEntityToLevelEntity[newEntity] = entity;
    }
}

Ideally there are 2 changes required here:
1 - The Relationship tracking components are made public so we do not need to resort to magic string name based reflection
2 - Relationship Components are somehow stored separately in the archetype to real components allowing for direct grabbing of the source components

@genaray genaray added the enhancement New feature or request label Sep 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: Todo
Development

No branches or pull requests

2 participants