Support FlatBuffer's Inline Structs
Opened this issue · 0 comments
duckdoom4 commented
Currently this isn't supported because the current implementation for 'structs' assume they are arrays with a reference field offset and VTable.
Inlined structs are more like value types that contain a byte buffer of 'struct' size.
This byte buffer has a wrapper type defining the layout of the byte buffer (aka. the struct), but without any vtables to help figure out the layout.
I have partial class laid out, but didn't have the time to complete it yet.
using System;
using System.Diagnostics;
namespace FlatCrawler.Lib;
/// <summary>
/// Node that contains an inlined serialized fixed size value struct.
/// </summary>
/// <inheritdoc cref="FlatBufferFieldValue<T>"/>
public sealed record FlatBufferInlineStruct : FlatBufferNodeField, ISchemaObserver
{
private DataRange StructDataMemory => new(Offset..(Offset + Size), $"{TypeName}[{Size}]", true);
// TODO:
// Add struct type as command arg
// Add command to reset struct layout
// Make struct editable, currently is a problem because the tree node printer expects entry count. Which is unknown for structs
// - structs start with fixed size
// - user can fill in types
// - struct will auto-adjust entries and size
// - Structs can't hold reference types, but can hold other inline structs
// Possibly I can just append one ghost entry to the tree.
// When `rf idex type` is called on this entry, it will create a real entry in place of the ghost one and create a new ghost entry when there's any space left
// The real entry is used to layout the struct
// FBStruct can be added to keep track of the members and field sizes.
// TODO: Structs can't contain reference types. The class type should reflect that
public FBClass StructInfo => (FBClass)FieldInfo.Type;
public override string TypeName { get => $"{StructInfo.TypeName}[{Size}]"; set { } }
private FlatBufferInlineStruct(FlatBufferFile file, int offset, int dataTableOffset, FlatBufferNode? parent) :
base(offset, null, dataTableOffset, parent)
{
FieldInfo = new FBFieldInfo { Type = new FBClass(file) };
RegisterStruct();
}
~FlatBufferInlineStruct()
{
UnRegisterStruct();
}
public override void TrackChildFieldNode(int fieldIndex, ReadOnlySpan<byte> data, TypeCode code, bool asArray, FlatBufferNode childNode)
{
StructInfo.SetMemberType(this, fieldIndex, data, code, asArray);
ref var member = ref Fields[fieldIndex];
member?.UnRegisterMemory();
member = childNode;
childNode.TrackFieldInfo(StructInfo.Members[fieldIndex]);
childNode.RegisterMemory();
}
public override void RegisterMemory()
{
FbFile.SetProtectedMemory(StructDataMemory);
StructInfo.RegisterMemory();
}
public override void UnRegisterMemory()
{
FbFile.RemoveProtectedMemory(StructDataMemory);
if (FieldInfo.Type is FBClass c)
c.UnRegisterMemory();
}
public override void TrackFieldInfo(FBFieldInfo sharedInfo)
{
UnRegisterStruct();
FieldInfo = sharedInfo;
RegisterStruct();
}
public override void TrackType(FBFieldInfo sharedInfo)
{
UnRegisterStruct();
FieldInfo = FieldInfo with { Type = sharedInfo.Type, Size = sharedInfo.Size };
RegisterStruct();
}
/// <summary>
/// Update all data based on the ObjectClass
/// </summary>
private void RegisterStruct()
{
StructInfo.AssociateVTable(VTable);
Fields = new FlatBufferNode[StructInfo.Members.Count];
StructInfo.Observers.Add(this);
}
/// <summary>
/// Remove event associations
/// </summary>
private void UnRegisterStruct()
{
StructInfo.Observers.Remove(this);
}
public void OnMemberCountChanged(int count)
{
if (Fields.Length == count)
return;
var tmp = new FlatBufferNode[count];
Fields.CopyTo(tmp, 0);
Fields = tmp;
}
public void OnMemberTypeChanged(MemberTypeChangedArgs e)
{
Debug.WriteLine($"Changing Member Type: {e.MemberIndex} {e.OldType} -> {e.NewType}");
if (!HasField(e.MemberIndex))
return;
ref var member = ref Fields[e.MemberIndex];
member?.UnRegisterMemory();
member = ReadNode(e.MemberIndex, e.Data, e.NewType.Type, e.FieldInfo.IsArray);
member.TrackFieldInfo(e.FieldInfo);
member.RegisterMemory();
}
public static FlatBufferInlineStruct Read(int offset, FlatBufferNodeField parent, int fieldIndex, ReadOnlySpan<byte> data, TypeCode type)
{
// TODO: Set correct params for data
return new FlatBufferInlineStruct(parent.FbFile, offset, 0, parent);
}
/// <summary>
/// Reads a new table node from the specified data.
/// </summary>
/// <param name="parent">The parent node.</param>
/// <param name="fieldIndex">The index of the field in the parent node.</param>
/// <param name="data">The data to read from.</param>
/// <param name="type">The type of the array.</param>
/// <returns>New child node.</returns>
public static FlatBufferInlineStruct Read(FlatBufferNodeField parent, int fieldIndex, ReadOnlySpan<byte> data, TypeCode type) => Read(parent.GetFieldOffset(fieldIndex), parent, fieldIndex, data, type);
}