using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using GlitchyEngine.Core;
using GlitchyEngine.Extensions;
using GlitchyEngine.Physics;
using System.Diagnostics.CodeAnalysis;
using GlitchyEngine.Editor;
namespace GlitchyEngine;
///
/// The base class for all entities and scripts in the current world.
///
public class Entity : EngineObject
{
///
/// Gets or sets the name of the .
///
public string Name
{
get => "TODO!";//ScriptGlue.Entity_GetName(_uuid);
set => ScriptGlue.Entity_SetName(_uuid, value);
}
///
/// Gets or sets the of the , which specify how the is displayed and interacted with in the editor.
///
public EditorFlags EditorFlags
{
get
{
ScriptGlue.Entity_GetEditorFlags(_uuid, out EditorFlags flags);
return flags;
}
set => ScriptGlue.Entity_SetEditorFlags(_uuid, value);
}
///
/// Only to be called by the engine. Don't call this constructor yourself, it will not result in a valid .
/// If you want to create a new use or
///
protected Entity()
{
// We don't do anything here.
// This constructor will be called by the Engine to initialize the scripts fields.
// Especially don't call Create here! Because Create would try and create a new entity.
}
///
/// Creates a new Entity.
///
/// The name of the new .
public Entity(string? name = null)
{
Create(name, null);
}
///
/// Creates a new with the specified components attached to it.
///
/// The name of the new .
/// The components that the will be created with.
public Entity(string? name, params Type[] components)
{
Create(name, components);
}
private void Create(string? name, Type[]? components)
{
ScriptGlue.Entity_Create(this, name, components, out _uuid);
if (_uuid == UUID.Zero)
{
throw new InvalidOperationException("Failed to create the Entity. Received UUID.Zero from the engine.");
}
}
///
/// Creates an instance that represents the with the given id.
///
/// The ID of the that belongs to this instance.
internal Entity(UUID uuid) : base(uuid) { }
#region Components
///
/// Returns if a component of the given type is attached to this .
///
/// The type of the component.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasComponent() => HasComponent(typeof(T));
///
/// Returns if a component of the given type is attached to this .
///
/// The type of the component.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasComponent(Type type) => ScriptGlue.Entity_HasComponent(_uuid, type);
///
/// Gets the component with the specified type.
///
/// The type of the component to get.
/// The component or null, if the doesn't have a component of the specified type.
public T? GetComponent() where T : Component, new()
{
if (HasComponent())
{
return new T
{
_uuid = _uuid
};
}
return null;
}
///
/// Gets the component with the specified type.
///
/// The type of the component to get.
/// If the entity has a of type then contains a reference to this component; otherwise it will contain after the method returned.
/// , if a component was retrieved; otherwise .
public bool TryGetComponent([MaybeNullWhen(false)] out T component) where T : Component, new()
{
component = GetComponent();
return component != null;
}
///
/// Gets the component with the given type.
///
/// The type of the component to get.
/// The component; or , if the doesn't have a component of the specified type or if doesn't inherit from .
/// For performance reasons it is recommended to use if possible as it might be slightly faster.
public Component? GetComponent(Type componentType)
{
if (!componentType.IsSubclassOf(typeof(Component)))
{
Log.Error($"{nameof(GetComponent)}: Invalid component type \"{componentType}\". Must be a subclass of \"{nameof(Component)}\".");
return null;
}
if (HasComponent(componentType))
{
return ActivatorExtension.CreateComponent(componentType, _uuid);
}
return null;
}
///
/// Attaches a component of the specified type to the .
///
/// The type of the component to attach.
/// The component.
public T AddComponent() where T : Component, new()
{
ScriptGlue.Entity_AddComponent(_uuid, typeof(T));
return new T
{
_uuid = _uuid
};
}
///
/// Attaches a component of the specified type to the .
///
/// The type of the component to attach.
/// The component that was attached to the ; or if doesn't inherit from .
/// For performance reasons it is recommended to use if possible as it might be slightly faster.
public Component? AddComponent(Type componentType)
{
if (!componentType.IsSubclassOf(typeof(Component)))
{
Log.Error($"{nameof(AddComponent)}: Invalid component type \"{componentType}\". Must be a subclass of \"{nameof(Component)}\".");
return null;
}
ScriptGlue.Entity_AddComponent(_uuid, componentType);
return ActivatorExtension.CreateComponent(componentType, _uuid);
}
///
/// Attaches components with the specified types to the .
///
/// The types of the components to attach.
/// An array containing the components that were attached. A component is , if it's type doesn't inherit from .
public Component?[] AddComponents(params Type[] componentTypes)
{
// Note: The elements in componentTypes are non-nullable externally, but internally we rely on nullability.
foreach ((Type componentType, int index) in componentTypes.WithIndex())
{
if (!componentType.IsSubclassOf(typeof(Component)))
{
Log.Error($"{nameof(AddComponents)}: Invalid component type \"{componentType}\" at index {index}. Must be a subclass of \"{nameof(Component)}\".");
// Setting null here is fine, the engine can handle it!
componentTypes[index] = null!;
}
}
ScriptGlue.Entity_AddComponents(_uuid, componentTypes);
Component?[] components = new Component[componentTypes.Length];
foreach ((Type componentType, int index) in componentTypes.WithIndex())
{
// componentType is null, if it's type is invalid!
if (componentType == null!)
continue;
components[index] = ActivatorExtension.CreateComponent(componentType, _uuid);
}
return components;
}
///
/// Attaches the specified components to the .
///
/// A tuple containing the added components.
public (T1, T2) AddComponents()
where T1 : Component, new()
where T2 : Component, new()
{
ScriptGlue.Entity_AddComponents(_uuid, new []{typeof(T1), typeof(T2)});
return (new T1 { _uuid = _uuid }, new T2 { _uuid = _uuid });
}
///
/// Attaches the specified components to the .
///
/// A tuple containing the added components.
public (T1, T2, T3) AddComponents()
where T1 : Component, new()
where T2 : Component, new()
where T3 : Component, new()
{
ScriptGlue.Entity_AddComponents(_uuid, new []{typeof(T1), typeof(T2), typeof(T3)});
return (new T1 { _uuid = _uuid }, new T2 { _uuid = _uuid }, new T3 { _uuid = _uuid });
}
///
/// Removes the component with the specified component type from the .
///
/// The type of the component to remove.
public void RemoveComponent() where T : Component, new()
{
ScriptGlue.Entity_RemoveComponent(_uuid, typeof(T));
}
///
/// Removes the component with the given component type from the .
///
/// The type of the component to remove.
public void RemoveComponent(Type componentType)
{
if (!componentType.IsSubclassOf(typeof(Component)))
{
Log.Error($"{nameof(RemoveComponent)}: Invalid component type \"{componentType}\". Must be a subclass of \"{nameof(Component)}\".");
return;
}
ScriptGlue.Entity_RemoveComponent(_uuid, componentType);
}
#endregion Components
///
/// Sets the script of the to the specified type.
/// If necessary removes the existing script.
///
/// The type of the script.
/// A reference to the new script or , if the operation failed.
public T? SetScript() where T : Entity
{
return ScriptGlue.Entity_SetScript(_uuid, typeof(T)) as T;
}
///
/// Removes the script from the .
///
public void RemoveScript()
{
ScriptGlue.Entity_RemoveScript(_uuid);
}
///
/// Gets the of this .
///
public Transform Transform => GetComponent()!; // Transform always exists!
///
/// Returns the first with the given name.
///
/// The name of the .
/// The first with the given name, or if no such entity exists.
public static Entity? FindEntityWithName(string name)
{
ScriptGlue.Entity_FindEntityWithName(name, out UUID entityId);
if (entityId == UUID.Zero)
return null;
return new Entity(entityId);
}
///
/// Returns whether or not the has a script of the specified type.
///
/// The type of the script.
/// if the has a script component of the given type; or if the either has no script or the script is not of the specified type.
public bool Is() where T : Entity
{
ScriptGlue.Entity_GetScriptInstance(_uuid, out object? scriptInstance);
return scriptInstance is T;
}
///
/// Returns whether or not the has a script of the specified type.
///
/// The type of the script.
/// if the has a script component of the given type; or if the either has no script or the script is not of the specified type.
public bool Is(Type type)
{
if (!type.IsSubclassOf(typeof(Entity)))
{
Log.Warning($"{nameof(Is)}: Invalid script type \"{type}\". Must be a subclass of \"{nameof(Entity)}\".");
return false;
}
ScriptGlue.Entity_GetScriptInstance(_uuid, out object? scriptInstance);
return type.IsInstanceOfType(scriptInstance);
}
///
/// Returns the script instance of the given type that is attached to the .
///
/// The type of the script.
/// The script instance of the given type; or if the has no script of the given type.
public T? As() where T : Entity
{
ScriptGlue.Entity_GetScriptInstance(_uuid, out object? scriptInstance);
return scriptInstance as T;
}
///
/// Returns the script instance of the given type that is attached to the .
///
/// The type of the script.
/// The script instance of the given type; or if the has no script of the given type.
public object? As(Type type)
{
if (!type.IsSubclassOf(typeof(Entity)))
{
Log.Error($"{nameof(As)}: Invalid script type \"{type}\". Must be a subclass of \"{nameof(Entity)}\".");
return null;
}
ScriptGlue.Entity_GetScriptInstance(_uuid, out object? scriptInstance);
return scriptInstance;
}
///
/// Returns the script instance of the given type that is attached to the with the given .
///
/// The id of the whose script instance shall be returned.
/// The type of the script.
/// The script instance of the given type; or if the with the given has no script of the given type.
internal static Entity? GetScriptReference(UUID id, Type type)
{
Debug.Assert(typeof(Entity).IsAssignableFrom(type));
ScriptGlue.Entity_GetScriptInstance(id, out object? scriptInstance);
return scriptInstance as Entity;
}
///
/// Destroys the and all it's children.
///
///
/// The destruction will not take place immediately. It will happen at the end of the current frame.
///
public void Destroy()
{
ScriptGlue.Entity_Destroy(_uuid);
}
///
/// Instantiates a copy of the given and all of it's children.
///
/// The to copy.
/// The new .
public static Entity CreateInstance(Entity entity)
{
ScriptGlue.Entity_CreateInstance(entity.UUID, out UUID newEntityId);
return new Entity(newEntityId);
}
///
/// Will be executed once after the entity has be created.
///
protected internal virtual void OnCreate() { }
///
/// Is called once every frame.
///
protected internal virtual void OnUpdate(float deltaTime) { }
///
/// Will be executed once when the entity is being destroyed.
///
protected internal virtual void OnDestroy() { }
}