Browse Source

Factories and tests

flabbet 1 year ago
parent
commit
ca2034b917

+ 9 - 13
src/PixiEditor.AvaloniaUI/Helpers/SerializationUtil.cs

@@ -1,4 +1,6 @@
 using ChunkyImageLib;
 using ChunkyImageLib;
+using PixiEditor.AvaloniaUI.Models.Serialization;
+using PixiEditor.AvaloniaUI.Models.Serialization.Factories;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core;
 using PixiEditor.Numerics;
 using PixiEditor.Numerics;
 using PixiEditor.Parser;
 using PixiEditor.Parser;
@@ -8,31 +10,25 @@ namespace PixiEditor.AvaloniaUI.Helpers;
 
 
 public static class SerializationUtil
 public static class SerializationUtil
 {
 {
-    public static object SerializeObject(object? value, ImageEncoder encoder)
+    public static object SerializeObject(object? value, SerializationConfig config, IReadOnlyList<SerializationFactory> allFactories)
     {
     {
         if (value is null)
         if (value is null)
         {
         {
             return null;
             return null;
         }
         }
 
 
-        if (value is Surface surface)
+        var factory = allFactories.FirstOrDefault(x => x.OriginalType == value.GetType());
+        
+        if (factory != null)
         {
         {
-            byte[] result = encoder.Encode(surface.ToByteArray(), surface.Size.X, surface.Size.Y);
-            IImageContainer container =
-                new ImageContainer { ImageBytes = result, ResourceOffset = 0, ResourceSize = result.Length };
-            return container;
+            return factory.Serialize(value);
         }
         }
-
+        
         if (value.GetType().IsValueType || value is string)
         if (value.GetType().IsValueType || value is string)
         {
         {
             return value;
             return value;
         }
         }
 
 
-        if (value is ISerializable serializable)
-        {
-            return serializable.Serialize();
-        }
-
-        throw new ArgumentException($"Type {value.GetType()} is not serializable and does not implement ISerializable");
+        throw new ArgumentException($"Type {value.GetType()} is not serializable and appropriate serialization factory was not found.");
     }
     }
 }
 }

+ 4 - 0
src/PixiEditor.AvaloniaUI/Helpers/ServiceCollectionHelpers.cs

@@ -14,6 +14,7 @@ using PixiEditor.AvaloniaUI.Models.IO.PaletteParsers.JascPalFile;
 using PixiEditor.AvaloniaUI.Models.Localization;
 using PixiEditor.AvaloniaUI.Models.Localization;
 using PixiEditor.AvaloniaUI.Models.Palettes;
 using PixiEditor.AvaloniaUI.Models.Palettes;
 using PixiEditor.AvaloniaUI.Models.Preferences;
 using PixiEditor.AvaloniaUI.Models.Preferences;
+using PixiEditor.AvaloniaUI.Models.Serialization.Factories;
 using PixiEditor.AvaloniaUI.ViewModels.Dock;
 using PixiEditor.AvaloniaUI.ViewModels.Dock;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.AvaloniaUI.ViewModels.Menu;
 using PixiEditor.AvaloniaUI.ViewModels.Menu;
@@ -111,6 +112,9 @@ internal static class ServiceCollectionHelpers
             .AddSingleton<IoFileType, BmpFileType>()
             .AddSingleton<IoFileType, BmpFileType>()
             .AddSingleton<IoFileType, GifFileType>()
             .AddSingleton<IoFileType, GifFileType>()
             .AddSingleton<IoFileType, Mp4FileType>()
             .AddSingleton<IoFileType, Mp4FileType>()
+            // Serialization Factories
+            .AddSingleton<SerializationFactory, SurfaceSerializationFactory>()
+            .AddSingleton<SerializationFactory, KernelSerializationFactory>()
             // Palette Parsers
             // Palette Parsers
             .AddSingleton<IPalettesProvider, PaletteProvider>()
             .AddSingleton<IPalettesProvider, PaletteProvider>()
             .AddSingleton<PaletteFileParser, JascFileParser>()
             .AddSingleton<PaletteFileParser, JascFileParser>()

+ 26 - 0
src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/KernelSerializationFactory.cs

@@ -0,0 +1,26 @@
+using PixiEditor.Numerics;
+
+namespace PixiEditor.AvaloniaUI.Models.Serialization.Factories;
+
+public class KernelSerializationFactory : SerializationFactory<SerializableKernel, Kernel>
+{
+    public KernelSerializationFactory(SerializationConfig config) : base(config)
+    {
+    }
+
+    public override SerializableKernel Serialize(Kernel original)
+    {
+        return new SerializableKernel
+        {
+            Width = original.Width,
+            Height = original.Height,
+            Values = original.AsSpan().ToArray()
+        };    
+    }
+
+    public override Kernel Deserialize(SerializableKernel serialized)
+    {
+        Kernel kernel = new(serialized.Width, serialized.Height, serialized.Values);
+        return kernel;
+    }
+}

+ 36 - 0
src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/SerializationFactory.cs

@@ -0,0 +1,36 @@
+namespace PixiEditor.AvaloniaUI.Models.Serialization.Factories;
+
+public abstract class SerializationFactory
+{
+    public abstract Type OriginalType { get; }
+    public abstract Type SerializedType { get; }
+    public abstract object Serialize(object original);
+    public abstract object Deserialize(object serialized);
+}
+
+public abstract class SerializationFactory<TSerializable, TOriginal> : SerializationFactory
+{
+    protected SerializationFactory(SerializationConfig config)
+    {
+        Config = config;
+    }
+
+    public SerializationConfig Config { get; }
+    
+    
+    public abstract TSerializable Serialize(TOriginal original);
+    public abstract TOriginal Deserialize(TSerializable serialized);
+    
+    public override object Serialize(object original)
+    {
+        return Serialize((TOriginal)original);
+    }
+    
+    public override object Deserialize(object serialized)
+    {
+        return Deserialize((TSerializable)serialized);
+    }
+    
+    public override Type OriginalType => typeof(TOriginal);
+    public override Type SerializedType => typeof(TSerializable);
+}

+ 27 - 0
src/PixiEditor.AvaloniaUI/Models/Serialization/Factories/SurfaceSerializationFactory.cs

@@ -0,0 +1,27 @@
+using PixiEditor.AvaloniaUI.Helpers;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.Parser;
+
+namespace PixiEditor.AvaloniaUI.Models.Serialization.Factories;
+
+public class SurfaceSerializationFactory : SerializationFactory<ImageContainer, Surface>
+{
+    public SurfaceSerializationFactory(SerializationConfig config) : base(config)
+    {
+    }
+
+    public override ImageContainer Serialize(Surface original)
+    {
+        var encoder = Config.Encoder;
+        byte[] result = encoder.Encode(original.ToByteArray(), original.Size.X, original.Size.Y);
+        ImageContainer container =
+            new ImageContainer { ImageBytes = result, ResourceOffset = 0, ResourceSize = result.Length };
+        
+        return container;
+    }
+
+    public override Surface Deserialize(ImageContainer serialized)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 16 - 0
src/PixiEditor.AvaloniaUI/Models/Serialization/SerializableKernel.cs

@@ -0,0 +1,16 @@
+using MessagePack;
+
+namespace PixiEditor.AvaloniaUI.Models.Serialization;
+
+[MessagePackObject]
+public class SerializableKernel
+{
+    [Key(0)]
+    public int Width { get; set; }
+    
+    [Key(1)]
+    public int Height { get; set; }
+    
+    [Key(2)]
+    public float[] Values { get; set; }
+}

+ 13 - 0
src/PixiEditor.AvaloniaUI/Models/Serialization/SerializationConfig.cs

@@ -0,0 +1,13 @@
+using PixiEditor.Parser.Skia;
+
+namespace PixiEditor.AvaloniaUI.Models.Serialization;
+
+public class SerializationConfig
+{
+    public ImageEncoder Encoder { get; set; }
+    
+    public SerializationConfig(ImageEncoder encoder)
+    {
+        Encoder = encoder;
+    }
+}

+ 16 - 9
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.Serialization.cs

@@ -2,10 +2,13 @@
 using System.Drawing;
 using System.Drawing;
 using ChunkyImageLib;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
+using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
 using PixiEditor.AvaloniaUI.Models.IO;
 using PixiEditor.AvaloniaUI.Models.IO;
 using PixiEditor.AvaloniaUI.Models.IO.FileEncoders;
 using PixiEditor.AvaloniaUI.Models.IO.FileEncoders;
+using PixiEditor.AvaloniaUI.Models.Serialization;
+using PixiEditor.AvaloniaUI.Models.Serialization.Factories;
 using PixiEditor.ChangeableDocument.Changeables;
 using PixiEditor.ChangeableDocument.Changeables;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
@@ -33,10 +36,13 @@ internal partial class DocumentViewModel
         Parser.Graph.NodeGraph graph = new();
         Parser.Graph.NodeGraph graph = new();
         ImageEncoder encoder = new QoiEncoder();
         ImageEncoder encoder = new QoiEncoder();
         var doc = Internals.Tracker.Document;
         var doc = Internals.Tracker.Document;
-
+        
         Dictionary<Guid, int> idMap = new();
         Dictionary<Guid, int> idMap = new();
 
 
-        AddNodes(doc.NodeGraph, graph, idMap, encoder);
+        List<SerializationFactory> factories =
+            ViewModelMain.Current.Services.GetServices<SerializationFactory>().ToList(); // a bit ugly, sorry
+
+        AddNodes(doc.NodeGraph, graph, idMap, new SerializationConfig(encoder), factories);
 
 
         var document = new PixiDocument
         var document = new PixiDocument
         {
         {
@@ -56,7 +62,7 @@ internal partial class DocumentViewModel
     }
     }
 
 
     private static void AddNodes(IReadOnlyNodeGraph graph, NodeGraph targetGraph, Dictionary<Guid, int> idMap,
     private static void AddNodes(IReadOnlyNodeGraph graph, NodeGraph targetGraph, Dictionary<Guid, int> idMap,
-        ImageEncoder encoder)
+        SerializationConfig config, IReadOnlyList<SerializationFactory> allFactories)
     {
     {
         targetGraph.AllNodes = new List<Node>();
         targetGraph.AllNodes = new List<Node>();
 
 
@@ -66,7 +72,7 @@ internal partial class DocumentViewModel
             idMap[node.Id] = id + 1;
             idMap[node.Id] = id + 1;
             id++;
             id++;
         }
         }
-        
+
         foreach (var node in graph.AllNodes)
         foreach (var node in graph.AllNodes)
         {
         {
             NodePropertyValue[] properties = new NodePropertyValue[node.InputProperties.Count];
             NodePropertyValue[] properties = new NodePropertyValue[node.InputProperties.Count];
@@ -76,14 +82,14 @@ internal partial class DocumentViewModel
                 properties[i] = new NodePropertyValue()
                 properties[i] = new NodePropertyValue()
                 {
                 {
                     PropertyName = node.InputProperties[i].InternalPropertyName,
                     PropertyName = node.InputProperties[i].InternalPropertyName,
-                    Value = SerializationUtil.SerializeObject(node.InputProperties[i].NonOverridenValue, encoder)
+                    Value = SerializationUtil.SerializeObject(node.InputProperties[i].NonOverridenValue, config, allFactories)
                 };
                 };
             }
             }
 
 
             Dictionary<string, object> additionalData = new();
             Dictionary<string, object> additionalData = new();
             node.SerializeAdditionalData(additionalData);
             node.SerializeAdditionalData(additionalData);
 
 
-            Dictionary<string, object> converted = ConvertToSerializable(additionalData, encoder);
+            Dictionary<string, object> converted = ConvertToSerializable(additionalData, config, allFactories);
 
 
             List<PropertyConnection> connections = new();
             List<PropertyConnection> connections = new();
 
 
@@ -117,7 +123,8 @@ internal partial class DocumentViewModel
 
 
     private static Dictionary<string, object> ConvertToSerializable(
     private static Dictionary<string, object> ConvertToSerializable(
         Dictionary<string, object> additionalData,
         Dictionary<string, object> additionalData,
-        ImageEncoder encoder)
+        SerializationConfig config,
+        IReadOnlyList<SerializationFactory> allFactories)
     {
     {
         Dictionary<string, object> converted = new();
         Dictionary<string, object> converted = new();
         foreach (var (key, value) in additionalData)
         foreach (var (key, value) in additionalData)
@@ -127,14 +134,14 @@ internal partial class DocumentViewModel
                 List<object> list = new();
                 List<object> list = new();
                 foreach (var item in enumerable)
                 foreach (var item in enumerable)
                 {
                 {
-                    list.Add(SerializationUtil.SerializeObject(item, encoder));
+                    list.Add(SerializationUtil.SerializeObject(item, config, allFactories));
                 }
                 }
 
 
                 converted[key] = list;
                 converted[key] = list;
             }
             }
             else
             else
             {
             {
-                converted[key] = SerializationUtil.SerializeObject(value, encoder);
+                converted[key] = SerializationUtil.SerializeObject(value, config, allFactories);
             }
             }
         }
         }
 
 

+ 1 - 1
src/PixiEditor.AvaloniaUI/ViewModels/ViewModelMain.cs

@@ -106,7 +106,7 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
     public ActionDisplayList ActionDisplays { get; }
     public ActionDisplayList ActionDisplays { get; }
     public bool UserWantsToClose { get; private set; }
     public bool UserWantsToClose { get; private set; }
 
 
-    public ViewModelMain(IServiceProvider serviceProvider)
+    public ViewModelMain()
     {
     {
         Current = this;
         Current = this;
         ActionDisplays = new ActionDisplayList(() => OnPropertyChanged(nameof(ActiveActionDisplay)));
         ActionDisplays = new ActionDisplayList(() => OnPropertyChanged(nameof(ActiveActionDisplay)));

+ 0 - 7
src/PixiEditor.Numerics/ISerializable.cs

@@ -1,7 +0,0 @@
-namespace PixiEditor.Numerics;
-
-public interface ISerializable
-{
-    public byte[] Serialize();
-    
-}

+ 11 - 20
src/PixiEditor.Numerics/Kernel.cs

@@ -2,7 +2,7 @@
 
 
 namespace PixiEditor.Numerics;
 namespace PixiEditor.Numerics;
 
 
-public class Kernel : ISerializable
+public class Kernel
 {
 {
     private KernelArray _buffer;
     private KernelArray _buffer;
 
 
@@ -38,6 +38,16 @@ public class Kernel : ISerializable
         Height = height;
         Height = height;
         _buffer = new KernelArray(width, height);
         _buffer = new KernelArray(width, height);
     }
     }
+    
+    public Kernel(int width, int height, float[] values)
+    {
+        if (width % 2 == 0)
+            throw new ArgumentException($"{width} must be odd", nameof(width));
+        
+        Width = width;
+        Height = height;
+        _buffer = new KernelArray(width, height, values);
+    }
 
 
     public static Kernel Identity(int width, int height) =>
     public static Kernel Identity(int width, int height) =>
         new(width, height) { [0, 0] = 1 };
         new(width, height) { [0, 0] = 1 };
@@ -67,23 +77,4 @@ public class Kernel : ISerializable
     }
     }
 
 
     public ReadOnlySpan<float> AsSpan() => _buffer.AsSpan();
     public ReadOnlySpan<float> AsSpan() => _buffer.AsSpan();
-    public byte[] Serialize()
-    {
-        Span<byte> data = stackalloc byte[Width * Height * sizeof(float) + sizeof(int) * 2];
-        BitConverter.GetBytes(Width).CopyTo(data);
-        BitConverter.GetBytes(Height).CopyTo(data[sizeof(int)..]); 
-        var span = AsSpan();
-        MemoryMarshal.Cast<float, byte>(span).CopyTo(data);
-        return data.ToArray();
-    }
-
-    public void Deserialize(byte[] data)
-    {
-        if (data.Length < sizeof(int) * 2)
-            throw new ArgumentException("Data is too short.", nameof(data));
-        
-        Width = BitConverter.ToInt32(data);
-        Height = BitConverter.ToInt32(data[sizeof(int)..]);
-        _buffer = new KernelArray(Width, Height, MemoryMarshal.Cast<byte, float>(data.AsSpan(sizeof(int) * 2)).ToArray());
-    }
 }
 }

+ 2 - 1
tests/ChunkyImageLibTest/ChunkyImageTests.cs

@@ -3,10 +3,11 @@ using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Bridge;
 using PixiEditor.DrawingApi.Core.Bridge;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surfaces;
 using PixiEditor.DrawingApi.Skia;
 using PixiEditor.DrawingApi.Skia;
 using PixiEditor.Numerics;
 using PixiEditor.Numerics;
 using Xunit;
 using Xunit;
+using static PixiEditor.DrawingApi.Core.Surface;
 
 
 namespace ChunkyImageLibTest;
 namespace ChunkyImageLibTest;
 public class ChunkyImageTests
 public class ChunkyImageTests

+ 1 - 0
tests/ChunkyImageLibTest/ImageOperationTests.cs

@@ -2,6 +2,7 @@
 using ChunkyImageLib;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using ChunkyImageLib.Operations;
+using PixiEditor.DrawingApi.Core;
 using PixiEditor.DrawingApi.Core.Bridge;
 using PixiEditor.DrawingApi.Core.Bridge;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Skia;
 using PixiEditor.DrawingApi.Skia;

+ 1 - 1
tests/PixiEditor.Backend.Tests/MockDocument.cs

@@ -1,6 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surfaces.ImageData;
 using PixiEditor.Numerics;
 using PixiEditor.Numerics;
 
 
 namespace PixiEditor.Backend.Tests;
 namespace PixiEditor.Backend.Tests;

+ 20 - 6
tests/PixiEditor.Backend.Tests/NodeSystemTests.cs

@@ -3,6 +3,8 @@ using System.Reflection;
 using ChunkyImageLib;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Helpers;
+using PixiEditor.AvaloniaUI.Models.Serialization;
+using PixiEditor.AvaloniaUI.Models.Serialization.Factories;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
@@ -114,9 +116,22 @@ public class NodeSystemTests
     }
     }
 
 
     [Fact]
     [Fact]
-    private void TestThatAllInputsAreSerializable()
+    private void TestThatAllInputsAreSerializableOrHaveFactories()
     {
     {
         var allNodeTypes = GetNodeTypesWithoutPairs();
         var allNodeTypes = GetNodeTypesWithoutPairs();
+        var allFoundFactories = typeof(SerializationFactory).Assembly.GetTypes()
+            .Where(x => x.IsAssignableTo(typeof(SerializationFactory))
+                        && x is { IsAbstract: false, IsInterface: false }).ToList();
+        
+        List<SerializationFactory> factories = new();
+        QoiEncoder encoder = new QoiEncoder();
+        SerializationConfig config = new SerializationConfig(encoder);
+        
+        foreach (var factoryType in allFoundFactories)
+        {
+            var factory = (SerializationFactory)Activator.CreateInstance(factoryType, config);
+            factories.Add(factory);
+        }
 
 
         IReadOnlyDocument target = new MockDocument();
         IReadOnlyDocument target = new MockDocument();
 
 
@@ -125,13 +140,12 @@ public class NodeSystemTests
             var node = NodeOperations.CreateNode(type, target);
             var node = NodeOperations.CreateNode(type, target);
             Assert.NotNull(node);
             Assert.NotNull(node);
 
 
-            QoiEncoder encoder = new QoiEncoder();
-
             foreach (var input in node.InputProperties)
             foreach (var input in node.InputProperties)
             {
             {
-                object? defaultOfType = Activator.CreateInstance(input.ValueType);
-                object serialized = SerializationUtil.SerializeObject(defaultOfType, encoder);
-                Assert.NotNull(serialized);
+                bool hasFactory = factories.Any(x => x.OriginalType == input.ValueType);
+                Assert.True(
+                    input.ValueType.IsValueType || input.ValueType == typeof(string) || hasFactory, 
+                    $"{input.ValueType} doesn't have a factory and is not serializable. Property: {input.InternalPropertyName}, NodeType: {node.GetType().Name}");
             }
             }
         }
         }
     }
     }