Skip to content

Commit

Permalink
Elementをコピーしたとき、Idが重複する可能性があるのを修正
Browse files Browse the repository at this point in the history
  • Loading branch information
yuto-trd committed Sep 1, 2023
1 parent f7d8682 commit 3633cdc
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 18 deletions.
2 changes: 2 additions & 0 deletions src/Beutl.Utilities/PooledArrayBufferWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ public Span<T> GetSpan(int sizeHint = 0)
return _buffer.AsSpan(_index);
}

public static T[] GetArray(PooledArrayBufferWriter<T> self) => self._buffer;

private void CheckAndResizeBuffer(int sizeHint)
{
if (sizeHint < 0)
Expand Down
97 changes: 97 additions & 0 deletions src/Beutl/Services/CoreObjectReborn.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;

using Beutl.Utilities;

namespace Beutl.Services;

public static class CoreObjectReborn
{
private const int DefaultGuidStringSize = 36;
private const int BufferSizeDefault = 16 * 1024;

private static void RebornCore<T>(T obj, PooledArrayBufferWriter<byte> output)
where T : class, ICoreObject, new()
{
var searcher = new ObjectSearcher(obj, v => v is ICoreObject);

Guid[] ids = searcher.SearchAll()
.Cast<ICoreObject>()
.Select(v => v.Id)
.Distinct()
.ToArray();

// JsonObjectに変換
var jsonObject = new JsonObject();
obj.WriteToJson(jsonObject);

// UTF-8に書き込む
JsonSerializerOptions options = JsonHelper.SerializerOptions;
var writerOptions = new JsonWriterOptions
{
Encoder = options.Encoder,
Indented = options.WriteIndented,
MaxDepth = options.MaxDepth
};

using (var writer = new Utf8JsonWriter(output, writerOptions))
{
jsonObject.WriteTo(writer, options);
}

// Idを置き換える
Span<byte> buffer = PooledArrayBufferWriter<byte>.GetArray(output).AsSpan().Slice(0, output.WrittenCount);
Span<byte> oldStr = stackalloc byte[DefaultGuidStringSize];
Span<byte> newStr = stackalloc byte[DefaultGuidStringSize];
foreach (Guid oldId in ids)
{
Guid newId = Guid.NewGuid();
GuidToUtf8(oldId, oldStr);
GuidToUtf8(newId, newStr);
Span<byte> localBuffer = buffer;

int index;
while ((index = localBuffer.IndexOf(oldStr)) >= 0)
{
localBuffer = localBuffer.Slice(index);
newStr.CopyTo(localBuffer);
}
}
}

public static void Reborn<T>(T obj, out T newInstance)
where T : class, ICoreObject, new()
{
using var output = new PooledArrayBufferWriter<byte>(BufferSizeDefault);
RebornCore(obj, output);

Span<byte> buffer = PooledArrayBufferWriter<byte>.GetArray(output).AsSpan().Slice(0, output.WrittenCount);

JsonObject jsonObj = JsonNode.Parse(buffer)!.AsObject();
var instance = new T();
instance.ReadFromJson(jsonObj);

newInstance = instance;
}

public static void Reborn<T>(T obj, out string json)
where T : class, ICoreObject, new()
{
using var output = new PooledArrayBufferWriter<byte>(BufferSizeDefault);
RebornCore(obj, output);

Span<byte> buffer = PooledArrayBufferWriter<byte>.GetArray(output).AsSpan().Slice(0, output.WrittenCount);
json = Encoding.UTF8.GetString(buffer);
}

private static void GuidToUtf8(Guid id, Span<byte> utf8)
{
Span<char> utf16 = stackalloc char[DefaultGuidStringSize];

if (!id.TryFormat(utf16, out _))
throw new Exception("Failed to 'Guid.TryFormat'.");

Encoding.UTF8.GetBytes(utf16, utf8);
}
}
12 changes: 4 additions & 8 deletions src/Beutl/ViewModels/ElementViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Beutl.Commands;
using Beutl.Models;
using Beutl.ProjectSystem;
using Beutl.Services;
using Beutl.Utilities;

using Reactive.Bindings;
Expand Down Expand Up @@ -236,9 +237,8 @@ private async ValueTask<bool> SetClipboard()
IClipboard? clipboard = App.GetClipboard();
if (clipboard != null)
{
var jsonNode = new JsonObject();
Model.WriteToJson(jsonNode);
string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions);
CoreObjectReborn.Reborn(Model, out string json);

var data = new DataObject();
data.Set(DataFormats.Text, json);
data.Set(Constants.Element, json);
Expand Down Expand Up @@ -317,11 +317,7 @@ private void OnSplit(TimeSpan timeSpan)
TimeSpan forwardLength = absTime - Model.Start;
TimeSpan backwardLength = Model.Length - forwardLength;

var jsonNode = new JsonObject();
Model.WriteToJson(jsonNode);
string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions);
var backwardLayer = new Element();
backwardLayer.ReadFromJson(JsonNode.Parse(json)!.AsObject());
CoreObjectReborn.Reborn(Model, out Element backwardLayer);

Scene.MoveChild(Model.ZIndex, Model.Start, forwardLength, Model).DoAndRecord(CommandRecorder.Default);
backwardLayer.Start = absTime;
Expand Down
4 changes: 0 additions & 4 deletions src/Beutl/Views/ElementView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ private void OnDataContextDetached(ElementViewModel obj)
obj.AnimationRequested = (_, _) => Task.CompletedTask;
_disposable1?.Dispose();
_disposable1 = null;

obj.SetClipboard(null);
}

private void OnDataContextAttached(ElementViewModel obj)
Expand Down Expand Up @@ -143,8 +141,6 @@ await Dispatcher.UIThread.InvokeAsync(async () =>

_disposable1 = obj.Model.GetObservable(Element.IsEnabledProperty)
.Subscribe(b => Dispatcher.UIThread.InvokeAsync(() => border.Opacity = b ? 1 : 0.5));

obj.SetClipboard(TopLevel.GetTopLevel(this)?.Clipboard);
}

protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
Expand Down
8 changes: 2 additions & 6 deletions src/Beutl/Views/MainView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -706,9 +706,7 @@ private void InitCommands(MainViewModel viewModel)
&& viewModel.Scene is Scene scene
&& viewModel.SelectedObject.Value is Element layer)
{
var jsonNode = new JsonObject();
layer.WriteToJson(jsonNode);
string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions);
CoreObjectReborn.Reborn(layer, out string json);
var data = new DataObject();
data.Set(DataFormats.Text, json);
data.Set(Constants.Element, json);
Expand All @@ -725,9 +723,7 @@ private void InitCommands(MainViewModel viewModel)
&& viewModel.Scene is Scene scene
&& viewModel.SelectedObject.Value is Element layer)
{
var jsonNode = new JsonObject();
layer.WriteToJson(jsonNode);
string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions);
CoreObjectReborn.Reborn(layer, out string json);
var data = new DataObject();
data.Set(DataFormats.Text, json);
data.Set(Constants.Element, json);
Expand Down

0 comments on commit 3633cdc

Please sign in to comment.