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

[Api] Add FindFirstObjectByType #61

Open
VaclavElias opened this issue Nov 6, 2023 · 6 comments
Open

[Api] Add FindFirstObjectByType #61

VaclavElias opened this issue Nov 6, 2023 · 6 comments
Labels
enhancement New feature or request p3 Medium priority, and does not prevent the core functionality

Comments

@VaclavElias
Copy link
Collaborator

VaclavElias commented Nov 6, 2023

From Nicogo in Discord:

https://discord.com/channels/500285081265635328/500292370923913222/1171202793193877534

public static class Ext
{
    public static T FindFirstObjectByType<T>(this Entity entity) where T : EntityComponent
    {
        return entity.Scene.Entities.Select(e => GetInMeOrChilds<T>(e)).FirstOrDefault(a => a != null);
    }
    public static T GetInMeOrChilds<T>(this Entity entity) where T : EntityComponent
    {
        var res = entity.Get<T>();
        if (res != null)
            return res;

        var childrens = entity.GetChildren();
        foreach (var child in childrens)
        {
            res = child.GetInMeOrChilds<T>();
            if (res != null)
                return res;
        }
        return null;
    }
}
@VaclavElias VaclavElias added the enhancement New feature or request label Nov 6, 2023
@VaclavElias
Copy link
Collaborator Author

And maybe these as well, also from Nicogo

public static class Extensions
{
public static T GetInMeOrParents<T>(this Entity entity) where T : EntityComponent
{
    while (entity != null)
    {
        var res = entity.Get<T>();
        if (res != null)
            return res;
        entity = entity.GetParent();
    }
    return null;
}
public static T GetInMeOrChilds<T>(this Entity entity) where T : EntityComponent
{
    var res = entity.Get<T>();
    if (res != null)
        return res;

    var childrens = entity.GetChildren();
    foreach (var child in childrens)
    {
        res = child.GetInMeOrChilds<T>();
        if (res != null)
            return res;
    }
    return null;
}

public static T GetInParents<T>(this Entity entity) where T : EntityComponent
{
    entity = entity.GetParent();
    while (entity != null)
    {
        var res = entity.Get<T>();
        if (res != null)
            return res;
        entity = entity.GetParent();
    }
    return null;
}
public static T GetInChilds<T>(this Entity entity) where T : EntityComponent
{
    var childrens = entity.GetChildren();
    foreach (var child in childrens)
    {
        var res = child.GetInMeOrChilds<T>();
        if (res != null)
            return res;
    }
    return null;
}
}

@Nicogo1705
Copy link

Nicogo1705 commented Nov 6, 2023

public static T FindFirstObjectByType<T>(this Entity entity) where T : EntityComponent
{
    foreach (var childEntity in entity.Scene.Entities)
    {
        var component = GetInMeOrChilds<T>(childEntity);
        if (component != null)
        {
            return component;
        }
    }

    return null;
}

Here is a little bit better version, also, we should add a note that it search only in the current Scene (don't know about child scene, it need testing) and also that it should not be used each frames

@VaclavElias
Copy link
Collaborator Author

VaclavElias commented Nov 6, 2023

Ok. Thanks, Yep, we can add all those remarks/comments.

@Nicogo1705
Copy link

And a way better code from Kryptos :

That method is recursive, which can cause issues (e.g. StackOverflowException) if entities are deeply nested. Better use an iterative approach such as:
// Note: this performs a depth-first iteration
// For a breadth-first iteration, replace Stack<T> with Queue<IEnumerable<T>>
public static IEnumerable<Entity> GetDescendants(this Entity entity)
{
    yield return entity; // If you want to return self; otherwise, comment this line

    // Create our own stack as to not use the runtime call stack
    var stack = new Stack<Entity>();
    foreach (var child in entity.GetChildren())
    {
        stack.Push(child);
        while (stack.Count > 0)
        {
            var descendant = stack.Pop();
            yield return descendant;
            foreach (var x in descendant.GetChildren()) stack.Push(x);
        }
    }
}

public static IEnumerable<T> GetComponentsInDescendants<T>(this Entity entity) where T : EntityComponent
{
    foreach (var descendant in entity.GetDescendants())
    {    
        if (descendant.Get<T>() is T component)
            yield return component;
    }
}

public static T FindFirstObjectByType<T>(this Entity entity) where T : EntityComponent
{
    return entity.GetComponentsInDescendants<T>().FirstOrDefault();
}

@Nicogo1705
Copy link

Ok, here is a full rewrite; it probably just need a PR.
Thanks to everybody on discord :)

  /// <summary>
  /// Provides extension methods for working with Entity hierarchies and components.
  /// </summary>
  public static class EntityExtensions
  {
      /// <summary>
      /// Returns all the descendants of the current Entity using a stack and yield return.
      /// </summary>
      /// <param name="entity">The current Entity.</param>
      /// <param name="includeMyself">Include the current Entity in the result.</param>
      /// <returns>An IEnumerable of descendant Entities.</returns>
      public static IEnumerable<Entity> GetDescendants(this Entity entity, bool includeMyself = false)
      {
          if (includeMyself)
              yield return entity;

          var stack = new Stack<Entity>();
          foreach (var child in entity.GetChildren())
          {
              stack.Push(child);
              while (stack.Count > 0)
              {
                  var descendant = stack.Pop();
                  yield return descendant;
                  foreach (var x in descendant.GetChildren()) stack.Push(x);
              }
          }
      }

      /// <summary>
      /// Returns all the parents of the current Entity using yield return.
      /// </summary>
      /// <param name="entity">The current Entity.</param>
      /// <param name="includeMyself">Include the current Entity in the result.</param>
      /// <returns>An IEnumerable of parent Entities.</returns>
      public static IEnumerable<Entity> GetParents(this Entity entity, bool includeMyself = false)
      {
          if (includeMyself)
              yield return entity;

          var parent = entity.GetParent();
          while (parent != null)
          {
              yield return parent;
              parent = entity.GetParent();
          }
      }

      /// <summary>
      /// Returns all the components of type 'T' in the descendants of the entity.
      /// </summary>
      /// <typeparam name="T">The type of EntityComponent to search for.</typeparam>
      /// <param name="entity">The current Entity.</param>
      /// <param name="includeMyself">Include the current Entity in the search.</param>
      /// <returns>An IEnumerable of components of type 'T' in the descendants of the entity.</returns>
      public static IEnumerable<T> GetComponentsInDescendants<T>(this Entity entity, bool includeMyself = false) where T : EntityComponent
      {
          foreach (var descendant in entity.GetDescendants(includeMyself))
          {
              if (descendant.Get<T>() is T component)
                  yield return component;
          }
      }

      /// <summary>
      /// Returns all the components of type 'T' in the parents of the entity.
      /// </summary>
      /// <typeparam name="T">The type of EntityComponent to search for.</typeparam>
      /// <param name="entity">The current Entity.</param>
      /// <param name="includeMyself">Include the current Entity in the search.</param>
      /// <returns>An IEnumerable of components of type 'T' in the parents of the entity.</returns>
      public static IEnumerable<T> GetComponentsInParents<T>(this Entity entity, bool includeMyself = false) where T : EntityComponent
      {
          foreach (var parent in entity.GetParents(includeMyself))
          {
              if (parent.Get<T>() is T component)
                  yield return component;
          }
      }

      /// <summary>
      /// Returns the first component of type 'T' found in any entity in the scene.
      /// </summary>
      /// <typeparam name="T">The type of EntityComponent to search for.</typeparam>
      /// <param name="entity">The current Entity.</param>
      /// <returns>The first component of type 'T' found in the scene, or null if none is found.</returns>
      public static T GetFirstComponentInScene<T>(this Entity entity) where T : EntityComponent
      {
          foreach (var childEntity in entity.Scene.Entities)
          {
              var components = childEntity.GetComponentsInDescendants<T>(true);
              if (components.FirstOrDefault() is T component)
                  return component;
          }
          return null;
      }

      /// <summary>
      /// Calls GetFirstComponentInScene, providing a Unity-like "wrapper."
      /// </summary>
      /// <typeparam name="T">The type of EntityComponent to search for.</typeparam>
      /// <param name="entity">The current Entity.</param>
      /// <returns>The first component of type 'T' found in the scene, or null if none is found.</returns>
      public static T FindFirstObjectByType<T>(this Entity entity) where T : EntityComponent
      {
          return entity.GetFirstComponentInScene<T>();
      }

      /// <summary>
      /// Note: These methods may perform deep traversal of the Entity hierarchy, which can be resource-intensive. Consider using them judiciously and avoid frequent use in performance-critical scenarios (e.g., in an Update method called every frame).
      /// </summary>
  }

@Nicogo1705
Copy link

Nicogo1705 commented Nov 21, 2023

Warning, there is a little typo :

public static IEnumerable<Entity> GetParents(this Entity entity, bool includeMyself = false)
      {
          if (includeMyself)
              yield return entity;

          var parent = entity.GetParent();
          while (parent != null)
          {
              yield return parent;
              parent = entity.GetParent();
          }
      }
      
      Should be 
      
       public static IEnumerable<Entity> GetParents(this Entity entity, bool includeMyself = false)
      {
          if (includeMyself)
              yield return entity;

          var parent = entity.GetParent();
          while (parent != null)
          {
              yield return parent;
              parent = parent.GetParent(); //Here
          }
      }```

@VaclavElias VaclavElias added the p3 Medium priority, and does not prevent the core functionality label Feb 18, 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 p3 Medium priority, and does not prevent the core functionality
Projects
None yet
Development

No branches or pull requests

2 participants