Gliner/Packaging/GlinerResources.cs
using System;
using System.Collections.Generic;

namespace GlinerPoc.Packaging;

/// <summary>
/// Binary payload holder. One instance per shard (<= ~32 MiB target) plus the
/// root's tokenizer/metadata blobs. The 64 MiB read cap is this project's own
/// defensive constant, not an engine limit.
/// </summary>
public sealed class GlinerBlob : BlobData
{
	public const int MaximumPayloadBytes = 64 * 1024 * 1024;

	[Property]
	public byte[] Bytes { get; set; } = Array.Empty<byte>();

	public override int Version => 1;

	public override void Serialize( ref Writer writer )
	{
		writer.Stream.WriteArray<byte>( Bytes );
	}

	public override void Deserialize( ref Reader reader )
	{
		Bytes = reader.Stream.ReadArray<byte>( MaximumPayloadBytes );
	}
}

/// <summary>
/// One SBGLI1 shard as a normal s&box asset. Payload is byte-identical to the
/// deterministic Python export; identity is the shard SHA-256.
/// </summary>
[AssetType( Name = "Gliner Model Shard", Extension = "glnshard", Category = "GLiNER" )]
public sealed partial class GlinerModelShardResource : GameResource
{
	[Property]
	public int ShardIndex { get; set; }

	[Property]
	public long ByteCount { get; set; }

	[Property]
	public string Sha256 { get; set; } = "";

	[Property]
	public GlinerBlob Payload { get; set; } = new();
}

/// <summary>
/// Typed reference from the root model resource to one shard. Typed references
/// (not string paths) are what make shards real package dependencies.
/// </summary>
public sealed class GlinerShardReference
{
	[Property]
	public GlinerModelShardResource Shard { get; set; }

	[Property]
	public int ShardIndex { get; set; }

	[Property]
	public long ByteCount { get; set; }

	[Property]
	public string Sha256 { get; set; } = "";
}

/// <summary>
/// Root GLiNER model resource: identity, runtime config (small JSON parsed
/// once at load), tokenizer bytes, SBGLI1 binary manifest (tensor/chunk/shard
/// records parsed once at load), and typed references to every shard.
/// </summary>
[AssetType( Name = "Gliner Model", Extension = "glinmdl", Category = "GLiNER" )]
public sealed partial class GlinerModelResource : GameResource
{
	public const string ExpectedMagic = "SBGLI1";
	public const int ExpectedFormatVersion = 1;

	[Property]
	public string FormatMagic { get; set; } = "";

	[Property]
	public int FormatVersion { get; set; }

	[Property]
	public string CheckpointSha256 { get; set; } = "";

	[Property]
	public string RuntimeConfigSha256 { get; set; } = "";

	[Property]
	public string RuntimeConfigJson { get; set; } = "";

	[Property]
	public string TokenizerSha256 { get; set; } = "";

	[Property]
	public int TokenizerByteCount { get; set; }

	[Property]
	public GlinerBlob TokenizerData { get; set; } = new();

	[Property]
	public GlinerBlob MetadataData { get; set; } = new();

	[Property]
	public List<GlinerShardReference> Shards { get; set; } = new();
}