Skip to content

Commit

Permalink
Merge pull request #1086 from b-editor/resolvable-by-id
Browse files Browse the repository at this point in the history
Enable Object Resolution by Id During Deserialization
  • Loading branch information
yuto-trd authored Sep 8, 2024
2 parents 02b5e85 + 0fafb3c commit d9f38bc
Show file tree
Hide file tree
Showing 26 changed files with 284 additions and 16 deletions.
7 changes: 6 additions & 1 deletion src/Beutl.Core/Collections/HierarchicalList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@ public HierarchicalList(IModifiableHierarchical parent)
Detached += item => parent.RemoveChild(item);
}

public IModifiableHierarchical Parent { get; }
public HierarchicalList()
{
ResetBehavior = ResetBehavior.Remove;
}

public IModifiableHierarchical? Parent { get; }
}
6 changes: 6 additions & 0 deletions src/Beutl.Core/CoreObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,12 @@ public virtual void Deserialize(ICoreSerializationContext context)
Optional<object?> value = item.RouteDeserialize(context);
if (value.HasValue)
{
if (value.Value is IReference { IsNull: false } reference)
{
context.Resolve(reference.Id,
resolved => SetValue(item, reference.Resolved((CoreObject)resolved)));
}

SetValue(item, value.Value);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Beutl.Core/Hierarchy/Hierarchical.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

namespace Beutl;

// TODO: 複数の親要素(参照元)を持てるようにする
public abstract class Hierarchical : CoreObject, IHierarchical, IModifiableHierarchical
{
public static readonly CoreProperty<IHierarchical?> HierarchicalParentProperty;
Expand Down
1 change: 1 addition & 0 deletions src/Beutl.Core/JsonHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public static void JsonRestore2(this ICoreSerializable serializable, string file
using (ThreadLocalSerializationContext.Enter(context))
{
serializable.Deserialize(context);
context.AfterDeserialized(serializable);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Beutl.Core/OptionalJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public override bool CanConvert(Type typeToConvert)
using (ThreadLocalSerializationContext.Enter(context))
{
serializable.Deserialize(context);
context.AfterDeserialized(serializable);
}

instance = serializable;
Expand Down
1 change: 0 additions & 1 deletion src/Beutl.Core/ProjectItemContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ public bool TryGetOrCreateItem(string file, [NotNullWhen(true)] out ProjectItem?

public void Add(ProjectItem item)
{
_app.Items.Add(item);
foreach (WeakReference<ProjectItem> wref in _items)
{
if (!wref.TryGetTarget(out _))
Expand Down
83 changes: 83 additions & 0 deletions src/Beutl.Core/Reference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
namespace Beutl;

public interface IReference
{
Guid Id { get; }

CoreObject? Value { get; }

bool IsNull { get; }

Type ObjectType { get; }

IReference Resolved(CoreObject obj);
}

public readonly struct Reference<TObject> : IEquatable<Reference<TObject>>, IReference
where TObject : notnull, CoreObject
{
private readonly TObject? _value;
private readonly Guid _id;

public Reference(Guid id) : this(id, null)
{
}

public Reference(TObject value) : this(value.Id, value)
{
}

public Reference(Guid id, TObject? value)
{
_id = id;
_value = value;
}

public Reference()
{
}

public Guid Id => _value?.Id ?? _id;

public TObject? Value => _value;

public bool IsNull => _id == Guid.Empty;

CoreObject? IReference.Value => Value;

Type IReference.ObjectType => typeof(TObject);

public Reference<TObject> Resolved(TObject obj)
{
return new Reference<TObject>(obj);
}

IReference IReference.Resolved(CoreObject obj)
{
return Resolved((TObject)obj);
}

public void Deconstruct(out Guid Id, out TObject? Value)
{
Id = this.Id;
Value = this.Value;
}

public bool Equals(Reference<TObject> other) => Id.Equals(other.Id);

public override bool Equals(object? obj) => obj is Reference<TObject> other && Equals(other);

public override int GetHashCode() => HashCode.Combine(_value, _id);

public static implicit operator Guid(Reference<TObject> reference) => reference.Id;

public static implicit operator Reference<TObject>(Guid id) => new(id);

public static implicit operator TObject?(Reference<TObject> reference) => reference.Value;

public static implicit operator Reference<TObject>(TObject value) => new(value.Id, value);

public static bool operator ==(Reference<TObject> left, Reference<TObject> right) => left.Equals(right);

public static bool operator !=(Reference<TObject> left, Reference<TObject> right) => !left.Equals(right);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public sealed class CoreSerializableJsonConverter : JsonConverter<ICoreSerializa
using (ThreadLocalSerializationContext.Enter(context))
{
instance.Deserialize(context);
context.AfterDeserialized(instance);
}

return instance;
Expand Down
2 changes: 2 additions & 0 deletions src/Beutl.Core/Serialization/ICoreSerializationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ public interface ICoreSerializationContext
bool Contains(string name);

void Populate(string name, ICoreSerializable obj);

void Resolve(Guid id, Action<ICoreSerializable> callback);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
Expand All @@ -25,7 +26,8 @@ private static void DeserializeArray(
else
{
string name = index.ToString();
output.Add(Deserialize(item, elementType, name, new RelaySerializationErrorNotifier(errorNotifier, name), parent));
output.Add(Deserialize(item, elementType, name,
new RelaySerializationErrorNotifier(errorNotifier, name), parent));
}

index++;
Expand Down Expand Up @@ -78,6 +80,7 @@ private static void DeserializeArray(
if (Activator.CreateInstance(actualType) is ICoreSerializable instance)
{
instance.Deserialize(context);
context.AfterDeserialized(instance);

return instance;
}
Expand All @@ -99,6 +102,12 @@ private static void DeserializeArray(
return ArrayTypeHelpers.ConvertArrayType(output, baseType, elementType);
}
}
else if (node is JsonValue jsonValue
&& jsonValue.TryGetValue(out Guid id)
&& baseType.IsAssignableTo(typeof(IReference)))
{
return Activator.CreateInstance(baseType, id);
}
}

ISerializationErrorNotifier? captured = LocalSerializationErrorNotifier.Current;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ public partial class JsonSerializationContext

return obj;
}
else if (value is IReference reference)
{
return reference.Id;
}
else if (value is JsonNode jsonNode)
{
return jsonNode;
Expand Down Expand Up @@ -115,7 +119,7 @@ public void SetValue<T>(string name, T? value)
else
{
Type actualType = value.GetType();
if (value is ICoreSerializable or IEnumerable)
if (value is ICoreSerializable or IEnumerable or IReference)
{
_json[name] = Serialize(name, value, actualType, typeof(T), ErrorNotifier, this);
}
Expand Down
72 changes: 69 additions & 3 deletions src/Beutl.Core/Serialization/JsonSerializationContext.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
using System.Text.Json.Nodes;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Nodes;

namespace Beutl.Serialization;

public partial class JsonSerializationContext(
Type ownerType, ISerializationErrorNotifier errorNotifier,
ICoreSerializationContext? parent = null, JsonObject? json = null)
Type ownerType,
ISerializationErrorNotifier errorNotifier,
ICoreSerializationContext? parent = null,
JsonObject? json = null)
: IJsonSerializationContext
{
public readonly Dictionary<string, (Type DefinedType, Type ActualType)> _knownTypes = [];
private List<(Guid, Action<ICoreSerializable>)>? _resolvers;
private Dictionary<Guid, ICoreSerializable>? _objects;
private readonly JsonObject _json = json ?? [];

public ICoreSerializationContext? Parent { get; } = parent;

public JsonSerializationContext Root => IsRoot ? this : (Parent as JsonSerializationContext)!.Root;

public CoreSerializationMode Mode => CoreSerializationMode.ReadWrite;

public Type OwnerType { get; } = ownerType;

[MemberNotNullWhen(false, nameof(Parent))]
public bool IsRoot => Parent == null;

public ISerializationErrorNotifier ErrorNotifier { get; } = errorNotifier;

public JsonObject GetJsonObject()
Expand Down Expand Up @@ -57,6 +67,62 @@ public void Populate(string name, ICoreSerializable obj)
}
}

public void AfterDeserialized(ICoreSerializable obj)
{
if (obj is CoreObject coreObject)
{
SetObjectAndId(coreObject);

if (IsRoot)
{
// Resolve references
if (_resolvers == null)
return;

for (int i = _resolvers.Count - 1; i >= 0; i--)
{
var (id, callback) = _resolvers[i];
if (_objects.TryGetValue(id, out var resolved))
{
callback(resolved);
}

_resolvers.RemoveAt(i);
}

// TODO: アプリケーション全体から解決できるようになれば、
// ここにそのコードを追加する。
}
}
}

[MemberNotNull(nameof(_objects))]
private void SetObjectAndId(CoreObject coreObject)
{
if (IsRoot)
{
_objects ??= new();
_objects[coreObject.Id] = coreObject;
}
else
{
Root.SetObjectAndId(coreObject);
}
}

Check warning on line 111 in src/Beutl.Core/Serialization/JsonSerializationContext.cs

View workflow job for this annotation

GitHub Actions / build

Member '_objects' must have a non-null value when exiting.

Check warning on line 111 in src/Beutl.Core/Serialization/JsonSerializationContext.cs

View workflow job for this annotation

GitHub Actions / build

Member '_objects' must have a non-null value when exiting.

Check warning on line 111 in src/Beutl.Core/Serialization/JsonSerializationContext.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Member '_objects' must have a non-null value when exiting.

Check warning on line 111 in src/Beutl.Core/Serialization/JsonSerializationContext.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Member '_objects' must have a non-null value when exiting.

Check warning on line 111 in src/Beutl.Core/Serialization/JsonSerializationContext.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Member '_objects' must have a non-null value when exiting.

Check warning on line 111 in src/Beutl.Core/Serialization/JsonSerializationContext.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Member '_objects' must have a non-null value when exiting.

Check warning on line 111 in src/Beutl.Core/Serialization/JsonSerializationContext.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Member '_objects' must have a non-null value when exiting.

Check warning on line 111 in src/Beutl.Core/Serialization/JsonSerializationContext.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Member '_objects' must have a non-null value when exiting.

public void Resolve(Guid id, Action<ICoreSerializable> callback)
{
if (IsRoot)
{
_resolvers ??= new();
_resolvers.Add((id, callback));
}
else
{
Parent.Resolve(id, callback);
}
}

public bool Contains(string name)
{
return _json.ContainsKey(name);
Expand Down
1 change: 1 addition & 0 deletions src/Beutl.Engine/Animation/AnimationSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ internal static class AnimationSerializer
using (ThreadLocalSerializationContext.Enter(innerContext))
{
animation.Deserialize(innerContext);
innerContext.AfterDeserialized(animation);
}
return animation;
}
Expand Down
1 change: 1 addition & 0 deletions src/Beutl.Engine/Converters/BrushJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public override IBrush Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS
using (ThreadLocalSerializationContext.Enter(context))
{
instance.Deserialize(context);
context.AfterDeserialized(instance);
}

return brush;
Expand Down
1 change: 1 addition & 0 deletions src/Beutl.Engine/Converters/KeyFrameJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public override IKeyFrame Read(ref Utf8JsonReader reader, Type typeToConvert, Js
using (ThreadLocalSerializationContext.Enter(context))
{
instance.Deserialize(context);
context.AfterDeserialized(instance);
}

return instance;
Expand Down
1 change: 1 addition & 0 deletions src/Beutl.Engine/Converters/PenJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public override IPen Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSer
using (ThreadLocalSerializationContext.Enter(context))
{
pen.Deserialize(context);
context.AfterDeserialized(pen);
}

return pen;
Expand Down
1 change: 1 addition & 0 deletions src/Beutl.Engine/Converters/TransformJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public override ITransform Read(ref Utf8JsonReader reader, Type typeToConvert, J
using (ThreadLocalSerializationContext.Enter(context))
{
instance.Deserialize(context);
context.AfterDeserialized(instance);
}

return transform;
Expand Down
1 change: 1 addition & 0 deletions src/Beutl.ProjectSystem/NodeTree/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ public override void Deserialize(ICoreSerializationContext context)
using (ThreadLocalSerializationContext.Enter(innerContext))
{
serializable.Deserialize(innerContext);
innerContext.AfterDeserialized(serializable);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public override void Deserialize(ICoreSerializationContext context)
using (ThreadLocalSerializationContext.Enter(innerContext))
{
serializable.Deserialize(innerContext);
innerContext.AfterDeserialized(serializable);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ public override void Deserialize(ICoreSerializationContext context)
using (ThreadLocalSerializationContext.Enter(innerContext))
{
nodeItem.Deserialize(innerContext);
innerContext.AfterDeserialized(nodeItem);
}

((NodeItem)nodeItem).LocalId = index;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public override void Deserialize(ICoreSerializationContext context)
using (ThreadLocalSerializationContext.Enter(innerContext))
{
serializable.Deserialize(innerContext);
innerContext.AfterDeserialized(serializable);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public override void Deserialize(ICoreSerializationContext context)
using (ThreadLocalSerializationContext.Enter(innerContext))
{
serializable.Deserialize(innerContext);
innerContext.AfterDeserialized(serializable);
}
}

Expand Down
Loading

0 comments on commit d9f38bc

Please sign in to comment.