kwsch/FlatCrawler

Support FlatBuffer's Inline Structs

Opened this issue · 0 comments

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&lt;T&gt;"/>
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);
}