|
@@ -0,0 +1,554 @@
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Linq;
|
|
|
+using System.Text;
|
|
|
+
|
|
|
+namespace Island.StandardLib.Storage
|
|
|
+{
|
|
|
+ public class PlayerPackage : IStorable
|
|
|
+ {
|
|
|
+ public static RunDevice Device { get; private set; }
|
|
|
+ static Dictionary<int, AbstractItemBehavior> ItemBehaviors;
|
|
|
+ static AbstractController Controller;
|
|
|
+
|
|
|
+ public static void DefineItemBehaviors(AbstractController controller, RunDevice device, params AbstractItemBehavior[] behaviors)
|
|
|
+ {
|
|
|
+ Device = device;
|
|
|
+ Controller = controller;
|
|
|
+ if (ItemBehaviors != null)
|
|
|
+ throw new Exception("ItemBehavior is already defined.");
|
|
|
+ ItemBehaviors = new Dictionary<int, AbstractItemBehavior>();
|
|
|
+ foreach (AbstractItemBehavior behavior in behaviors)
|
|
|
+ {
|
|
|
+ if (ItemBehaviors.ContainsKey(behavior.Id))
|
|
|
+ throw new Exception($"Redefined ItemBehavior, Id = {behavior.Id}");
|
|
|
+ ItemBehaviors.Add(behavior.Id, behavior);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static AbstractItemBehavior GetItemBehavior(int id)
|
|
|
+ {
|
|
|
+ if (ItemBehaviors == null)
|
|
|
+ throw new Exception("ItemBehavior is undefined. Use DefineItemBehaviors(...) to set behaviors first.");
|
|
|
+ if (ItemBehaviors.TryGetValue(id, out AbstractItemBehavior behavior))
|
|
|
+ return behavior;
|
|
|
+ throw new Exception($"GetItemBehavior() Use undefined item behavior Id = {id}");
|
|
|
+ }
|
|
|
+
|
|
|
+ public class ItemData : IStorable
|
|
|
+ {
|
|
|
+ public int DataType;
|
|
|
+ public byte[] Data;
|
|
|
+
|
|
|
+ public MultData DataVisitor
|
|
|
+ {
|
|
|
+ get => new MultData(Data);
|
|
|
+ set => Data = value.Data;
|
|
|
+ }
|
|
|
+
|
|
|
+ public ItemData()
|
|
|
+ {
|
|
|
+ DataType = 0;
|
|
|
+ Data = new byte[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ public ItemData(int dataType, MultData data)
|
|
|
+ {
|
|
|
+ DataType = dataType;
|
|
|
+ Data = data.Data;
|
|
|
+ }
|
|
|
+
|
|
|
+ public ItemData(object data, int iStorageType = 10)
|
|
|
+ {
|
|
|
+ if (data is MultData)
|
|
|
+ {
|
|
|
+ DataVisitor = (MultData)data;
|
|
|
+ DataType = 7;
|
|
|
+ }
|
|
|
+ else if (data is byte[])
|
|
|
+ {
|
|
|
+ DataVisitor = new MultData((byte[])data);
|
|
|
+ DataType = 7;
|
|
|
+ }
|
|
|
+ else if (data is int)
|
|
|
+ {
|
|
|
+ DataVisitor = new MultData((int)data);
|
|
|
+ DataType = 1;
|
|
|
+ }
|
|
|
+ else if (data is float)
|
|
|
+ {
|
|
|
+ DataVisitor = new MultData((float)data);
|
|
|
+ DataType = 2;
|
|
|
+ }
|
|
|
+ else if (data is string)
|
|
|
+ {
|
|
|
+ DataVisitor = new MultData((string)data);
|
|
|
+ DataType = 0;
|
|
|
+ }
|
|
|
+ else if (data is long)
|
|
|
+ {
|
|
|
+ DataVisitor = new MultData((long)data);
|
|
|
+ DataType = 3;
|
|
|
+ }
|
|
|
+ else if (data is uint)
|
|
|
+ {
|
|
|
+ DataVisitor = new MultData((uint)data);
|
|
|
+ DataType = 5;
|
|
|
+ }
|
|
|
+ else if (data is ulong)
|
|
|
+ {
|
|
|
+ DataVisitor = new MultData((ulong)data);
|
|
|
+ DataType = 6;
|
|
|
+ }
|
|
|
+ else if (data is double)
|
|
|
+ {
|
|
|
+ DataVisitor = new MultData((double)data);
|
|
|
+ DataType = 4;
|
|
|
+ }
|
|
|
+ else if (data is IStorable)
|
|
|
+ {
|
|
|
+ DataVisitor = new MultData((IStorable)data);
|
|
|
+ DataType = iStorageType;
|
|
|
+ }
|
|
|
+ else throw new InvalidCastException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool IsString => DataType == 0;
|
|
|
+ public bool IsInt => DataType == 1;
|
|
|
+ public bool IsFloat => DataType == 2;
|
|
|
+ public bool IsLong => DataType == 3;
|
|
|
+ public bool IsDouble => DataType == 4;
|
|
|
+ public bool IsUInt => DataType == 5;
|
|
|
+ public bool IsULong => DataType == 6;
|
|
|
+ public bool IsByteArray => DataType == 7;
|
|
|
+ public bool IsIStorable => DataType >= 10;
|
|
|
+
|
|
|
+ public T As<T>() where T : IStorable, new()
|
|
|
+ {
|
|
|
+ DataStorage ds = new DataStorage(Data);
|
|
|
+ T t = new T();
|
|
|
+ t.ReadFromData(ds);
|
|
|
+ return t;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void SetData<T>(T data) where T : IStorable, new()
|
|
|
+ {
|
|
|
+ DataStorage ds = new DataStorage();
|
|
|
+ data.WriteToData(ds);
|
|
|
+ Data = ds.Bytes;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void ReadFromData(DataStorage data)
|
|
|
+ {
|
|
|
+ data.ReadUncheck(out DataType);
|
|
|
+ Data = data.Read();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void WriteToData(DataStorage data)
|
|
|
+ {
|
|
|
+ data.WriteUncheck(DataType);
|
|
|
+ data.Write(Data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class Item : IStorable
|
|
|
+ {
|
|
|
+ public Item() : this(0, 0) { }
|
|
|
+
|
|
|
+ public Item(int typeId, int instanceId, int stackCount = 1, StorableFixedArray<ItemData> data = null)
|
|
|
+ {
|
|
|
+ Data = data ?? new StorableFixedArray<ItemData>();
|
|
|
+ TypeId = typeId;
|
|
|
+ InstanceId = instanceId;
|
|
|
+ StackCount = stackCount;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int TypeId;
|
|
|
+ public int InstanceId;
|
|
|
+ public int StackCount;
|
|
|
+ public StorableFixedArray<ItemData> Data;
|
|
|
+
|
|
|
+ public ItemData this[int index] => Data[index];
|
|
|
+ public int Length => Data.Length;
|
|
|
+
|
|
|
+ public void ConsumeOne(ConnectionPlayerBase who, PlayerPackage package)
|
|
|
+ {
|
|
|
+ StackCount--;
|
|
|
+ ServerSetModify(who, package);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void ServerSetModify(ConnectionPlayerBase who, PlayerPackage package)
|
|
|
+ {
|
|
|
+ if (Device == RunDevice.Client) throw new Exception("SetModify only can use in Server mode.");
|
|
|
+ if (StackCount <= 0)
|
|
|
+ {
|
|
|
+ lock (package.objects) package.objects.Remove(InstanceId);
|
|
|
+ Controller.OnServerSendDeleteCommand(who, InstanceId);
|
|
|
+ }
|
|
|
+ else Controller.OnServerSendModifyCommand(who, this);
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void ClientApplyChange(Item item)
|
|
|
+ {
|
|
|
+ TypeId = item.TypeId;
|
|
|
+ StackCount = item.StackCount;
|
|
|
+ Data = item.Data;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool CanUse => GetItemBehavior(TypeId).CanUse(this);
|
|
|
+
|
|
|
+ public void ServerUse(ConnectionPlayerBase who)
|
|
|
+ {
|
|
|
+ if (!CanUse) return;
|
|
|
+ GetItemBehavior(TypeId).ServerUse(who, this);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void ClientUse()
|
|
|
+ {
|
|
|
+ if (!CanUse) return;
|
|
|
+ GetItemBehavior(TypeId).ClientUse(this);
|
|
|
+ Controller.OnClientSendUseCommand(InstanceId);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void WriteToData(DataStorage data)
|
|
|
+ {
|
|
|
+ data.WriteUncheck(TypeId);
|
|
|
+ data.WriteUncheck(InstanceId);
|
|
|
+ data.WriteUncheck(StackCount);
|
|
|
+ data.Write(Data);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void ReadFromData(DataStorage data)
|
|
|
+ {
|
|
|
+ data.ReadUncheck(out TypeId);
|
|
|
+ data.ReadUncheck(out InstanceId);
|
|
|
+ data.ReadUncheck(out StackCount);
|
|
|
+ data.Read(out Data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void OnServerReceiveUseCommand(ConnectionPlayerBase who, int instanceId)
|
|
|
+ {
|
|
|
+ Item item = FindItem(instanceId);
|
|
|
+ if (item == null)
|
|
|
+ Logger.Log(LogLevel.Error, $"Player {who.Nick} trying to use a nonexists item. InstanceId {instanceId}");
|
|
|
+ else item.ServerUse(who);
|
|
|
+ }
|
|
|
+
|
|
|
+ public abstract class AbstractItemBehavior
|
|
|
+ {
|
|
|
+ public abstract int Id { get; }
|
|
|
+ public abstract void ServerUse(ConnectionPlayerBase who, Item item);
|
|
|
+ public abstract void ClientUse(Item item);
|
|
|
+ public abstract bool CanUse(Item item);
|
|
|
+ public abstract int CanStack(Item root, Item append);
|
|
|
+ }
|
|
|
+
|
|
|
+ public abstract class AbstractUnstackableItemBehavior : AbstractItemBehavior
|
|
|
+ {
|
|
|
+ public override int CanStack(Item root, Item append) => 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ public abstract class AbstractStackableItemBehavior : AbstractItemBehavior
|
|
|
+ {
|
|
|
+ public abstract int MaxStackSize { get; }
|
|
|
+
|
|
|
+ protected virtual bool CompareStackCondition(Item root, Item append)
|
|
|
+ {
|
|
|
+ return root.Data.GetBytes().ByteEquals(append.Data.GetBytes());
|
|
|
+ }
|
|
|
+
|
|
|
+ public override int CanStack(Item root, Item append)
|
|
|
+ {
|
|
|
+ if (!CompareStackCondition(root, append)) return 0;
|
|
|
+ return MaxStackSize - root.StackCount;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public abstract class AbstractController
|
|
|
+ {
|
|
|
+ public abstract void OnServerSendModifyCommand(ConnectionPlayerBase who, Item changedItem);
|
|
|
+ public abstract void OnServerSendDeleteCommand(ConnectionPlayerBase who, int itemInstanceId);
|
|
|
+ public abstract void OnServerSendChangeMaxItemSizeCommand(ConnectionPlayerBase who, int newMaxItemSizeValue);
|
|
|
+ public abstract void OnClientSendUseCommand(int itemInstanceId);
|
|
|
+ }
|
|
|
+
|
|
|
+ public PlayerPackage() : this(30) { }
|
|
|
+
|
|
|
+ public PlayerPackage(int maxItemSize)
|
|
|
+ {
|
|
|
+ if (ItemBehaviors == null)
|
|
|
+ throw new Exception("ItemBehavior is undefined. Use DefineItemBehaviors(...) to set behaviors first.");
|
|
|
+ objects = new StorableDictionary<SInt, Item>();
|
|
|
+ idAllocator = 0;
|
|
|
+ this.maxItemSize = maxItemSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ public StorableDictionary<SInt, Item> objects;
|
|
|
+ int maxItemSize;
|
|
|
+ int idAllocator;
|
|
|
+
|
|
|
+ public void SetMaxItemSize(int newValue, ConnectionPlayerBase who = null)
|
|
|
+ {
|
|
|
+ if (Device == RunDevice.Server)
|
|
|
+ {
|
|
|
+ if (maxItemSize == newValue) return;
|
|
|
+ if (maxItemSize > newValue) throw new ArgumentException("MaxItemSize: new value must be upper than current value.");
|
|
|
+ maxItemSize = newValue;
|
|
|
+ Controller.OnServerSendChangeMaxItemSizeCommand(who, newValue);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ maxItemSize = newValue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public int MaxItemSize => maxItemSize;
|
|
|
+
|
|
|
+ public int ItemSpace
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ lock (objects)
|
|
|
+ return objects.Count;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public int EmptySpace => maxItemSize - ItemSpace;
|
|
|
+
|
|
|
+ public int CreateItem(ConnectionPlayerBase who, int typeId, int stackCount = 1, StorableFixedArray<ItemData> data = null)
|
|
|
+ {
|
|
|
+ if (Device == RunDevice.Client) throw new Exception("CreateItem only can use in Server mode.");
|
|
|
+ Item thisObject = new Item(typeId, -1, stackCount, data == null ? null : data.MemoryCopy());
|
|
|
+ lock (objects)
|
|
|
+ {
|
|
|
+ foreach (var t in objects)
|
|
|
+ {
|
|
|
+ if (t.Value.TypeId == thisObject.TypeId)
|
|
|
+ {
|
|
|
+ int canStackCount = GetItemBehavior(t.Value.TypeId).CanStack(t.Value, thisObject);
|
|
|
+ canStackCount = canStackCount <= thisObject.StackCount ? canStackCount : thisObject.StackCount;
|
|
|
+ if (canStackCount > 0)
|
|
|
+ {
|
|
|
+ t.Value.StackCount += canStackCount;
|
|
|
+ if (who != null)
|
|
|
+ t.Value.ServerSetModify(who, this);
|
|
|
+ thisObject.StackCount -= canStackCount;
|
|
|
+ }
|
|
|
+ if (thisObject.StackCount == 0)
|
|
|
+ {
|
|
|
+ thisObject = null;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (thisObject != null)
|
|
|
+ {
|
|
|
+ int singleStk = GetItemBehavior(typeId).CanStack(new Item(typeId, 0, 0, data), new Item(typeId, 0, thisObject.StackCount, data));
|
|
|
+ do
|
|
|
+ {
|
|
|
+ if (objects.Count >= maxItemSize)
|
|
|
+ return thisObject.StackCount;
|
|
|
+ int curStack = thisObject.StackCount > singleStk ? singleStk : thisObject.StackCount;
|
|
|
+ Item curItem = new Item(typeId, idAllocator++, curStack, data);
|
|
|
+ objects.Add(curItem.InstanceId, curItem);
|
|
|
+ if (who != null)
|
|
|
+ curItem.ServerSetModify(who, this);
|
|
|
+ thisObject.StackCount -= curStack;
|
|
|
+ }
|
|
|
+ while (thisObject.StackCount > 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Item FindItem(int instanceId)
|
|
|
+ {
|
|
|
+ if (objects.TryGetValue(instanceId, out Item item))
|
|
|
+ return item;
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int CreateItem(ConnectionPlayerBase who, Item copyItem)
|
|
|
+ {
|
|
|
+ return CreateItem(who, copyItem.TypeId, copyItem.StackCount, copyItem.Data);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void DeleteItemWithInstance(ConnectionPlayerBase who, int itemInstanceId, int stackCount = 1)
|
|
|
+ {
|
|
|
+ if (Device == RunDevice.Client) throw new Exception("DeleteItem only can use in Server mode.");
|
|
|
+ lock (objects)
|
|
|
+ {
|
|
|
+ if (objects.TryGetValue(itemInstanceId, out Item it))
|
|
|
+ {
|
|
|
+ if (it.StackCount > stackCount)
|
|
|
+ {
|
|
|
+ it.StackCount -= stackCount;
|
|
|
+ it.ServerSetModify(who, this);
|
|
|
+ }
|
|
|
+ else if (it.StackCount == stackCount)
|
|
|
+ {
|
|
|
+ objects.Remove(itemInstanceId);
|
|
|
+ Controller.OnServerSendDeleteCommand(who, itemInstanceId);
|
|
|
+ }
|
|
|
+ else throw new Exception($"Trying delete InstanceId = {itemInstanceId} with StackCount = {stackCount}, no such stack count.");
|
|
|
+ }
|
|
|
+ else throw new Exception($"The InstanceId = {itemInstanceId} is not founded.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void DeleteItemWithType(ConnectionPlayerBase who, int typeId, int stackCount = 1, Func<Item, bool> checker = null)
|
|
|
+ {
|
|
|
+ if (Device == RunDevice.Client) throw new Exception("DeleteItem only can use in Server mode.");
|
|
|
+ lock (objects)
|
|
|
+ {
|
|
|
+ List<int> preDeletes = new List<int>();
|
|
|
+ foreach (var obj in objects)
|
|
|
+ {
|
|
|
+ if (obj.Value.TypeId == typeId)
|
|
|
+ {
|
|
|
+ if (checker != null)
|
|
|
+ {
|
|
|
+ if (!checker(obj.Value))
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (obj.Value.StackCount > stackCount)
|
|
|
+ {
|
|
|
+ obj.Value.StackCount -= stackCount;
|
|
|
+ stackCount = 0;
|
|
|
+ if (who != null)
|
|
|
+ obj.Value.ServerSetModify(who, this);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ stackCount -= obj.Value.StackCount;
|
|
|
+ preDeletes.Add(obj.Key);
|
|
|
+ if (who != null)
|
|
|
+ Controller.OnServerSendDeleteCommand(who, obj.Key);
|
|
|
+ if (stackCount == 0) break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (stackCount != 0)
|
|
|
+ throw new Exception("DeleteItem: FATAL ERROR: Count is not enough, but still maybe reduce some stack count, that already caught item lost.");
|
|
|
+ for (int i = 0; i < preDeletes.Count; i++)
|
|
|
+ objects.Remove(preDeletes[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool CheckItems(int typeId, int stackCount = 1, Func<Item, bool> checker = null)
|
|
|
+ {
|
|
|
+ lock (objects)
|
|
|
+ {
|
|
|
+ foreach (var obj in objects)
|
|
|
+ {
|
|
|
+ if (obj.Value.TypeId == typeId)
|
|
|
+ {
|
|
|
+ if (checker != null)
|
|
|
+ {
|
|
|
+ if (!checker(obj.Value))
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (obj.Value.StackCount > stackCount)
|
|
|
+ {
|
|
|
+ stackCount = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ stackCount -= obj.Value.StackCount;
|
|
|
+ if (stackCount == 0) break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return stackCount == 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class ItemRequest
|
|
|
+ {
|
|
|
+ public struct ItemQueryInfo
|
|
|
+ {
|
|
|
+ public ItemQueryInfo(int typeId, int stackCount = 1, Func<Item, bool> checker = null)
|
|
|
+ {
|
|
|
+ TypeId = typeId;
|
|
|
+ StackCount = stackCount;
|
|
|
+ Checker = checker;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int TypeId { get; private set; }
|
|
|
+ public int StackCount { get; private set; }
|
|
|
+ public Func<Item, bool> Checker { get; private set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ ItemQueryInfo[] Requests;
|
|
|
+ public ItemRequest(params ItemQueryInfo[] infos) => Requests = infos;
|
|
|
+
|
|
|
+ public bool CheckIsEnough(PlayerPackage package)
|
|
|
+ {
|
|
|
+ bool isEnough = true;
|
|
|
+ lock (package.objects)
|
|
|
+ {
|
|
|
+ for (int i = 0; i < Requests.Length; i++)
|
|
|
+ isEnough &= package.CheckItems(Requests[i].TypeId, Requests[i].StackCount, Requests[i].Checker);
|
|
|
+ }
|
|
|
+ return isEnough;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool Consume(ConnectionPlayerBase who, PlayerPackage package)
|
|
|
+ {
|
|
|
+ lock (package.objects)
|
|
|
+ {
|
|
|
+ if (CheckIsEnough(package))
|
|
|
+ {
|
|
|
+ for (int i = 0; i < Requests.Length; i++)
|
|
|
+ package.DeleteItemWithType(who, Requests[i].TypeId, Requests[i].StackCount, Requests[i].Checker);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void OnClientReceivedModify(Item changedItem, out Item added)
|
|
|
+ {
|
|
|
+ lock (objects)
|
|
|
+ {
|
|
|
+ if (objects.TryGetValue(changedItem.InstanceId, out Item item))
|
|
|
+ {
|
|
|
+ item.ClientApplyChange(changedItem);
|
|
|
+ added = null;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ objects.Add(changedItem.InstanceId, changedItem);
|
|
|
+ added = changedItem;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void OnClientReceivedDelete(int itemInstanceId)
|
|
|
+ {
|
|
|
+ lock (objects)
|
|
|
+ {
|
|
|
+ if (objects.ContainsKey(itemInstanceId))
|
|
|
+ objects.Remove(itemInstanceId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void ReadFromData(DataStorage data)
|
|
|
+ {
|
|
|
+ data.Read(out idAllocator);
|
|
|
+ data.Read(out objects);
|
|
|
+ data.ReadUncheck(out maxItemSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void WriteToData(DataStorage data)
|
|
|
+ {
|
|
|
+ data.Write(idAllocator);
|
|
|
+ data.Write(objects);
|
|
|
+ data.WriteUncheck(maxItemSize);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|