Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CodeSmile-0000011110110111/9bc7d884ddba6a8bb733e6b656d239b5 to your computer and use it in GitHub Desktop.
Save CodeSmile-0000011110110111/9bc7d884ddba6a8bb733e6b656d239b5 to your computer and use it in GitHub Desktop.
Unity.Serialization for NativeList<UnsafeList<T>> with adapters
// Copyright (C) 2021-2023 Steffen Itterheim
// Refer to included LICENSE file for terms and conditions.
using NUnit.Framework;
using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Serialization.Binary;
using UnityEngine;
using ChunkCoord = Unity.Mathematics.int2;
using ChunkSize = Unity.Mathematics.int2;
using ChunkKey = System.Int64;
using CellSize = Unity.Mathematics.float3;
using CellGap = Unity.Mathematics.float3;
using GridCoord = Unity.Mathematics.int3;
using WorldPos = Unity.Mathematics.float3;
using ListCount = System.UInt16;
namespace CodeSmile.Tests.Editor.ProTiler
{
public class NestedNativeCollectionSerializationTests
{
[Test] public void CanSerializeLinearTileDataStruct()
{
var linearData = new LinearTileData(1, TileFlags.DirectionWest);
var bytes = BinarySerializer.Serialize(linearData);
Assert.NotNull(bytes);
Debug.Log($"{bytes.Length} Bytes: {bytes.AsString()}");
Assert.That(bytes.Length, Is.EqualTo(sizeof(UInt32)));
}
[Test] public void CanSerializeAndDeserializeLinearTileDataStruct()
{
var linearData = new LinearTileData(123, TileFlags.DirectionEast | TileFlags.FlipBoth);
var bytes = BinarySerializer.Serialize(linearData);
var deserializedData = BinarySerializer.Deserialize<LinearTileData>(bytes);
Assert.That(deserializedData, Is.EqualTo(linearData));
}
[Test] public void CanSerializeAndDeserializeNativeListOfLinearTileDataStruct()
{
var linearData = new LinearTileData(2, TileFlags.DirectionSouth);
var list = new NativeList<LinearTileData>(1, Allocator.Temp);
list.Add(linearData);
var adapters = new List<IBinaryAdapter>
{
new BinaryAdapters.NativeListAdapter<LinearTileData>(Allocator.Temp),
};
var bytes = BinarySerializer.Serialize(list, adapters);
var deserialList = BinarySerializer.Deserialize<NativeList<LinearTileData>>(bytes, adapters);
Debug.Log($"{bytes.Length} Bytes: {bytes.AsString()}");
Assert.That(deserialList.Length, Is.EqualTo(1));
Assert.That(deserialList[0], Is.EqualTo(linearData));
}
[Test] public void CanSerializeAndDeserializeUnsafeListOfLinearTileDataStruct()
{
var linearData = new LinearTileData(2, TileFlags.DirectionSouth);
var list = new UnsafeList<LinearTileData>(1, Allocator.Temp);
list.Add(linearData);
var adapters = new List<IBinaryAdapter>
{
new BinaryAdapters.UnsafeListAdapter<LinearTileData>(Allocator.Temp),
};
var bytes = BinarySerializer.Serialize(list, adapters);
var deserialList = BinarySerializer.Deserialize<UnsafeList<LinearTileData>>(bytes, adapters);
Debug.Log($"{bytes.Length} Bytes: {bytes.AsString()}");
Assert.That(deserialList.Length, Is.EqualTo(1));
Assert.That(deserialList[0], Is.EqualTo(linearData));
}
[Test] public void CanSerializeAndDeserializeNativeListOfUnsafeListOfLinearTileDataStruct()
{
var linearData1 = new LinearTileData(2, TileFlags.DirectionSouth);
var linearData2 = new LinearTileData(3, TileFlags.DirectionWest);
var list = new NativeList<UnsafeList<LinearTileData>>(2, Allocator.Temp);
var list0 = new UnsafeList<LinearTileData>(1, Allocator.Temp);
var list1 = new UnsafeList<LinearTileData>(1, Allocator.Temp);
list0.Add(linearData1);
list1.Add(linearData2);
list.Add(list0);
list.Add(list1);
Assert.That(list0[0], Is.EqualTo(linearData1));
Assert.That(list1[0], Is.EqualTo(linearData2));
var adapters = new List<IBinaryAdapter>
{
new BinaryAdapters.NativeListAdapter<UnsafeList<LinearTileData>>(Allocator.Temp),
new BinaryAdapters.UnsafeListAdapter<LinearTileData>(Allocator.Temp),
};
var bytes = BinarySerializer.Serialize(list, adapters);
var deserialList = BinarySerializer.Deserialize<NativeList<UnsafeList<LinearTileData>>>(bytes, adapters);
Debug.Log($"{bytes.Length} Bytes: {bytes.AsString()}");
Assert.That(deserialList.Length, Is.EqualTo(2));
var deserialList0 = deserialList[0];
var deserialList1 = deserialList[1];
Assert.That(deserialList0[0], Is.EqualTo(linearData1));
Assert.That(deserialList1[0], Is.EqualTo(linearData2));
}
[Test] public void CanSerializeAndDeserializeTilemap3D()
{
var allocator = Allocator.Temp;
var chunkSize = new ChunkSize(4, 4, 4);
var chunks = new NativeParallelHashMap<Int64,
TilemapChunk<LinearTileData, SparseTileData>>(0, allocator);
var chunk = new TilemapChunk<LinearTileData, SparseTileData>(chunkSize, allocator);
chunks.Add(0, chunk);
var adapters = new List<IBinaryAdapter>
{
new BinaryAdapters.NativeParallelHashMapAdapter<Int64,
TilemapChunk<LinearTileData, SparseTileData>>(Allocator.Temp),
new BinaryAdapters.NativeListAdapter<UnsafeList<LinearTileData>>(Allocator.Temp),
new BinaryAdapters.UnsafeListAdapter<LinearTileData>(Allocator.Temp),
};
var bytes = BinarySerializer.Serialize(chunks, adapters);
var deserialChunks =
BinarySerializer.Deserialize<NativeParallelHashMap<Int64,
TilemapChunk<LinearTileData, SparseTileData>>>(bytes, adapters);
Debug.Log($"{bytes.Length} Bytes: {bytes.AsString()}");
Assert.That(deserialChunks.Count(), Is.EqualTo(1));
}
public static class BinaryAdapters
{
public class NativeListAdapter<T> : IBinaryAdapter<NativeList<T>> where T : unmanaged
{
private readonly Allocator m_Allocator;
public NativeListAdapter(Allocator allocator) => m_Allocator = allocator;
public unsafe void Serialize(in BinarySerializationContext<NativeList<T>> context, NativeList<T> list)
{
context.Writer->Add(list.Length);
var itemCount = list.Length;
for (var i = 0; i < itemCount; i++)
context.SerializeValue(list[i]);
}
public unsafe NativeList<T> Deserialize(in BinaryDeserializationContext<NativeList<T>> context)
{
var itemCount = context.Reader->ReadNext<Int32>();
var list = CreateResizedNativeList(itemCount, m_Allocator);
for (var i = 0; i < itemCount; i++)
list[i] = context.DeserializeValue<T>();
return list;
}
private NativeList<T> CreateResizedNativeList(Int32 itemCount, Allocator allocator)
{
var list = new NativeList<T>(itemCount, allocator);
list.Resize(itemCount, NativeArrayOptions.UninitializedMemory);
return list;
}
}
public class UnsafeListAdapter<T> : IBinaryAdapter<UnsafeList<T>> where T : unmanaged
{
private readonly Allocator m_Allocator;
public UnsafeListAdapter(Allocator allocator) => m_Allocator = allocator;
public unsafe void Serialize(in BinarySerializationContext<UnsafeList<T>> context, UnsafeList<T> list)
{
context.Writer->Add(list.Length);
var itemCount = list.Length;
for (var i = 0; i < itemCount; i++)
context.SerializeValue(list[i]);
}
public unsafe UnsafeList<T> Deserialize(in BinaryDeserializationContext<UnsafeList<T>> context)
{
var itemCount = context.Reader->ReadNext<Int32>();
var list = CreateResizedUnsafeList(itemCount, m_Allocator);
for (var i = 0; i < itemCount; i++)
list[i] = context.DeserializeValue<T>();
return list;
}
private UnsafeList<T> CreateResizedUnsafeList(Int32 itemCount, Allocator allocator)
{
var list = new UnsafeList<T>(itemCount, allocator);
list.Resize(itemCount);
return list;
}
}
public class NativeParallelHashMapAdapter<TKey, TValue> :
IBinaryAdapter<NativeParallelHashMap<TKey, TValue>>
where TKey : unmanaged, IEquatable<TKey> where TValue : unmanaged
{
private readonly Allocator m_Allocator;
public NativeParallelHashMapAdapter(Allocator allocator) => m_Allocator = allocator;
public unsafe void Serialize(in BinarySerializationContext<NativeParallelHashMap<TKey, TValue>> context,
NativeParallelHashMap<TKey, TValue> list)
{
context.Writer->Add(list.Count());
foreach (var pair in list)
{
context.SerializeValue(pair.Key);
context.SerializeValue(pair.Value);
}
}
public unsafe NativeParallelHashMap<TKey, TValue> Deserialize(
in BinaryDeserializationContext<NativeParallelHashMap<TKey, TValue>> context)
{
var itemCount = context.Reader->ReadNext<Int32>();
var map = new NativeParallelHashMap<TKey, TValue>(itemCount, m_Allocator);
for (var i = 0; i < itemCount; i++)
{
var key = context.DeserializeValue<TKey>();
var value = context.DeserializeValue<TValue>();
map[key] = value;
}
return map;
}
}
}
}
}
@CodeSmile-0000011110110111
Copy link
Author

Output of the NativeList<UnsafeList> test code (brackets mine):

20 Bytes: 2000<1000<2040>><1000<3080>>
2 items in the enclosing NativeList
<1 item in each UnsafeList>
<<values 2 + 4 for linearData1>>
<<values 3 + 8 for linearData2>>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment