ECS refactor: new ComponentSet, serialization, generators

Major ECS API overhaul: added ComponentSet, refactored ComponentRegistry, and updated all entity/component creation methods. Introduced robust custom serialization infrastructure and per-component source generators for registration and (de)serialization. Updated editor, engine, and test code to use new APIs. Improved code quality, naming, and performance throughout. Removed obsolete code and updated dependencies.
This commit is contained in:
2025-12-20 20:41:40 +09:00
parent 3118021272
commit 00b4e82ded
60 changed files with 1216 additions and 814 deletions

View File

@@ -19,12 +19,14 @@ public sealed partial class PropertyField : ContentControl
{ typeof(RangeBase), RangeBase.ValueProperty },
};
private object? sourceObject;
private FieldInfo? propertyInfo;
private Type? _fieldType;
private object? _sourceObject;
internal FieldInfo? _propertyInfo;
internal Type? _fieldType;
private object? _lastValue;
public event Action<PropertyField>? OnValueChanged;
public string Label
{
get => (string)GetValue(LabelProperty);
@@ -59,8 +61,8 @@ public sealed partial class PropertyField : ContentControl
private static TField ConfigureField<TField>(PropertyField propertyField, FieldInfo fieldInfo, object sourceObject, Func<TField> factory)
where TField : FrameworkElement
{
propertyField.sourceObject = sourceObject;
propertyField.propertyInfo = fieldInfo;
propertyField._sourceObject = sourceObject;
propertyField._propertyInfo = fieldInfo;
propertyField._fieldType = typeof(TField);
var field = factory();
@@ -72,6 +74,12 @@ public sealed partial class PropertyField : ContentControl
Path = new PropertyPath(fieldInfo.Name),
Mode = BindingMode.TwoWay,
});
field.RegisterPropertyChangedCallback(dp, (s, e) =>
{
propertyField.OnValueChanged?.Invoke(propertyField);
});
return field;
}
@@ -82,42 +90,31 @@ public sealed partial class PropertyField : ContentControl
Label = label
};
FrameworkElement content;
switch (fieldInfo.FieldType)
FrameworkElement content = fieldInfo.FieldType switch
{
case Type t when t == typeof(string):
content = ConfigureField(propertyField, fieldInfo, sourceObject, () => new TextBox());
break;
case Type t when t == typeof(int) || t == typeof(float) || t == typeof(double):
content = ConfigureField(propertyField, fieldInfo, sourceObject, () => new NumberBox
Type t when t == typeof(string) => ConfigureField(propertyField, fieldInfo, sourceObject, () => new TextBox()),
Type t when t == typeof(int) || t == typeof(float) || t == typeof(double) => ConfigureField(propertyField, fieldInfo, sourceObject, () => new NumberBox
{
SpinButtonPlacementMode = NumberBoxSpinButtonPlacementMode.Hidden,
AcceptsExpression = true,
NumberFormatter = new DecimalFormatter
{
SpinButtonPlacementMode = NumberBoxSpinButtonPlacementMode.Hidden,
AcceptsExpression = true,
NumberFormatter = new DecimalFormatter
{
FractionDigits = t == typeof(int) ? 0 : 9,
}
});
break;
case Type t when t == typeof(bool):
content = ConfigureField(propertyField, fieldInfo, sourceObject, () => new ToggleSwitch());
break;
case Type t when t == typeof(Enum):
content = ConfigureField(propertyField, fieldInfo, sourceObject, () => new ComboBox
{
ItemsSource = Enum.GetValues(t),
SelectedValuePath = "Value",
});
break;
default:
content = new TextBlock
{
Text = $"Unsupported type: {fieldInfo.FieldType.Name}",
VerticalAlignment = VerticalAlignment.Center,
Foreground = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Red)
};
break;
}
FractionDigits = t == typeof(int) ? 0 : 9,
}
}),
Type t when t == typeof(bool) => ConfigureField(propertyField, fieldInfo, sourceObject, () => new ToggleSwitch()),
Type t when t == typeof(Enum) => ConfigureField(propertyField, fieldInfo, sourceObject, () => new ComboBox
{
ItemsSource = Enum.GetValues(t),
SelectedValuePath = "Value",
}),
_ => new TextBlock
{
Text = $"Unsupported type: {fieldInfo.FieldType.Name}",
VerticalAlignment = VerticalAlignment.Center,
Foreground = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Red)
},
};
propertyField.Content = content;
return propertyField;
@@ -125,12 +122,12 @@ public sealed partial class PropertyField : ContentControl
public void UpdateValue()
{
if (sourceObject == null || propertyInfo == null || _fieldType == null)
if (_sourceObject == null || _propertyInfo == null || _fieldType == null)
{
return;
}
var currentValue = propertyInfo.GetValue(sourceObject);
var currentValue = _propertyInfo.GetValue(_sourceObject);
if (Equals(currentValue, _lastValue))
{
return;
@@ -139,7 +136,7 @@ public sealed partial class PropertyField : ContentControl
var dp = GetValueProperty(_fieldType);
if (dp != null)
{
SetValue(dp, propertyInfo.GetValue(sourceObject));
SetValue(dp, _propertyInfo.GetValue(_sourceObject));
_lastValue = currentValue;
}
}

View File

@@ -4,10 +4,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Ghost.Editor.Core.Controls.Internal">
<Style TargetType="local:ComponentDataView">
<Style TargetType="local:ComponentView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ComponentDataView">
<ControlTemplate TargetType="local:ComponentView">
<StackPanel Margin="0,0,0,16">
<Border
Padding="8"

View File

@@ -1,15 +1,15 @@
using Ghost.Core;
using Ghost.Editor.Core.Inspector;
using Ghost.Editor.Core.Resources;
using Ghost.Editor.Core.Utilities;
using Ghost.SparseEntities;
using Ghost.Entities;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Ghost.Editor.Core.Controls.Internal;
internal unsafe sealed partial class ComponentDataView : Control
internal sealed unsafe partial class ComponentView : Control
{
private delegate void EditorUpdate();
@@ -18,6 +18,10 @@ internal unsafe sealed partial class ComponentDataView : Control
private readonly World? _world;
private readonly Entity _entity = Entity.Invalid;
private readonly Type? _componentType;
private readonly ComponentInfo _componentInfo;
private object? _managedInstance;
private void* _pComponentData;
private ComponentEditor? _customEditor;
private PropertyField[]? _propertyFields;
@@ -30,11 +34,11 @@ internal unsafe sealed partial class ComponentDataView : Control
}
public static readonly DependencyProperty HeaderTextProperty =
DependencyProperty.Register(nameof(HeaderText), typeof(string), typeof(ComponentDataView), new PropertyMetadata(string.Empty));
DependencyProperty.Register(nameof(HeaderText), typeof(string), typeof(ComponentView), new PropertyMetadata(string.Empty));
internal ComponentDataView()
internal ComponentView()
{
DefaultStyleKey = typeof(ComponentDataView);
DefaultStyleKey = typeof(ComponentView);
Unloaded += (s, e) =>
{
@@ -46,12 +50,14 @@ internal unsafe sealed partial class ComponentDataView : Control
};
}
public ComponentDataView(string header, World world, Entity entity, Type componentType) : this()
public ComponentView(string header, World world, Entity entity, Type componentType) : this()
{
HeaderText = header;
_world = world;
_entity = entity;
_componentType = componentType;
_componentInfo = ComponentRegistry.GetComponentInfo(componentType);
}
protected override void OnApplyTemplate()
@@ -77,7 +83,7 @@ internal unsafe sealed partial class ComponentDataView : Control
private void CustomEditorUpdate()
{
_customEditor!.Update();
_customEditor?.Update();
}
public void ReBuild()
@@ -93,6 +99,14 @@ internal unsafe sealed partial class ComponentDataView : Control
return;
}
if (_propertyFields != null)
{
foreach (var propertyField in _propertyFields)
{
propertyField.OnValueChanged -= OnPropertyValueChanged;
}
}
var componentObject = new ComponentObject(_world, _entity);
var editorType = TypeCache.GetTypes().FirstOrDefault(t =>
typeof(ComponentEditor).IsAssignableFrom(t) &&
@@ -106,18 +120,21 @@ internal unsafe sealed partial class ComponentDataView : Control
}
else
{
var fields = _componentType.GetFields(StaticResource.componentPropertyBindingFlags);
var fields = _componentType.GetFields(StaticResource.ComponentPropertyBindingFlags);
_propertyFields = new PropertyField[fields.Length];
_pComponentData = _world.EntityManager.GetComponent(_entity, _componentInfo.id);
_managedInstance = Marshal.PtrToStructure((nint)_pComponentData, _componentType);
if (_managedInstance == null)
{
return;
}
for (var i = 0; i < fields.Length; i++)
{
var field = fields[i];
if (!_world.ComponentStorage.TryGetPool(TypeHandle.Get(_componentType), out var pool))
{
continue;
}
var component = pool.Get(_entity);
var propertyField = PropertyField.Create(field.Name, field, component);
var propertyField = PropertyField.Create(field.Name, field, _managedInstance);
propertyField.OnValueChanged += OnPropertyValueChanged;
_propertyFields[i] = propertyField;
_contentContainer.Children.Add(propertyField);
@@ -126,19 +143,15 @@ internal unsafe sealed partial class ComponentDataView : Control
_editorUpdate = _customEditor == null ? ReflectionUpdate : CustomEditorUpdate;
_editorUpdate();
_world.ComponentChanged += OnComponentChanged;
}
private void OnComponentChanged(World world, Entity entity, Type type)
private void OnPropertyValueChanged(PropertyField field)
{
if (world != _world
|| entity != _entity
|| type != _componentType)
if (_managedInstance == null || _pComponentData == null)
{
return;
}
_editorUpdate?.Invoke();
Marshal.StructureToPtr(_managedInstance, (nint)_pComponentData, false);
}
}