ProtobufTest/Assets/Plugs/protobuf-net/Serializers/MapDecorator.cs

299 lines
12 KiB
C#

using ProtoBuf.Meta;
using System;
#if FEAT_COMPILER
using ProtoBuf.Compiler;
#endif
using System.Collections.Generic;
using System.Reflection;
namespace ProtoBuf.Serializers
{
class MapDecorator<TDictionary, TKey, TValue> : ProtoDecoratorBase where TDictionary : class, IDictionary<TKey, TValue>
{
private readonly Type concreteType;
private readonly IProtoSerializer keyTail;
private readonly int fieldNumber;
private readonly WireType wireType;
internal MapDecorator(TypeModel model, Type concreteType, IProtoSerializer keyTail, IProtoSerializer valueTail,
int fieldNumber, WireType wireType, WireType keyWireType, WireType valueWireType, bool overwriteList)
: base(DefaultValue == null
? (IProtoSerializer)new TagDecorator(2, valueWireType, false, valueTail)
: (IProtoSerializer)new DefaultValueDecorator(model, DefaultValue, new TagDecorator(2, valueWireType, false, valueTail)))
{
this.wireType = wireType;
this.keyTail = new DefaultValueDecorator(model, DefaultKey, new TagDecorator(1, keyWireType, false, keyTail));
this.fieldNumber = fieldNumber;
this.concreteType = concreteType ?? typeof(TDictionary);
if (keyTail.RequiresOldValue) throw new InvalidOperationException("Key tail should not require the old value");
if (!keyTail.ReturnsValue) throw new InvalidOperationException("Key tail should return a value");
if (!valueTail.ReturnsValue) throw new InvalidOperationException("Value tail should return a value");
AppendToCollection = !overwriteList;
}
private static readonly MethodInfo indexerSet = GetIndexerSetter();
private static MethodInfo GetIndexerSetter()
{
#if PROFILE259
foreach(var prop in typeof(TDictionary).GetRuntimeProperties())
#else
foreach (var prop in typeof(TDictionary).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
#endif
{
if (prop.Name != "Item") continue;
if (prop.PropertyType != typeof(TValue)) continue;
var args = prop.GetIndexParameters();
if (args == null || args.Length != 1) continue;
if (args[0].ParameterType != typeof(TKey)) continue;
#if PROFILE259
var method = prop.SetMethod;
#else
var method = prop.GetSetMethod(true);
#endif
if (method != null)
{
return method;
}
}
throw new InvalidOperationException("Unable to resolve indexer for map");
}
private static readonly TKey DefaultKey = (typeof(TKey) == typeof(string)) ? (TKey)(object)"" : default(TKey);
private static readonly TValue DefaultValue = (typeof(TValue) == typeof(string)) ? (TValue)(object)"" : default(TValue);
public override Type ExpectedType => typeof(TDictionary);
public override bool ReturnsValue => true;
public override bool RequiresOldValue => AppendToCollection;
private bool AppendToCollection { get; }
public override object Read(object untyped, ProtoReader source)
{
TDictionary typed = AppendToCollection ? ((TDictionary)untyped) : null;
if (typed == null) typed = (TDictionary)Activator.CreateInstance(concreteType);
do
{
var key = DefaultKey;
var value = DefaultValue;
SubItemToken token = ProtoReader.StartSubItem(source);
int field;
while ((field = source.ReadFieldHeader()) > 0)
{
switch (field)
{
case 1:
key = (TKey)keyTail.Read(null, source);
break;
case 2:
value = (TValue)Tail.Read(Tail.RequiresOldValue ? (object)value : null, source);
break;
default:
source.SkipField();
break;
}
}
ProtoReader.EndSubItem(token, source);
typed[key] = value;
} while (source.TryReadFieldHeader(fieldNumber));
return typed;
}
public override void Write(object untyped, ProtoWriter dest)
{
foreach (var pair in (TDictionary)untyped)
{
ProtoWriter.WriteFieldHeader(fieldNumber, wireType, dest);
var token = ProtoWriter.StartSubItem(null, dest);
if (pair.Key != null) keyTail.Write(pair.Key, dest);
if (pair.Value != null) Tail.Write(pair.Value, dest);
ProtoWriter.EndSubItem(token, dest);
}
}
#if FEAT_COMPILER
protected override void EmitWrite(CompilerContext ctx, Local valueFrom)
{
Type itemType = typeof(KeyValuePair<TKey, TValue>);
MethodInfo moveNext, current, getEnumerator = ListDecorator.GetEnumeratorInfo(ctx.Model,
ExpectedType, itemType, out moveNext, out current);
Type enumeratorType = getEnumerator.ReturnType;
MethodInfo key = itemType.GetProperty(nameof(KeyValuePair<TKey, TValue>.Key)).GetGetMethod(),
@value = itemType.GetProperty(nameof(KeyValuePair<TKey, TValue>.Value)).GetGetMethod();
using (Compiler.Local list = ctx.GetLocalWithValue(ExpectedType, valueFrom))
using (Compiler.Local iter = new Compiler.Local(ctx, enumeratorType))
using (Compiler.Local token = new Compiler.Local(ctx, typeof(SubItemToken)))
using (Compiler.Local kvp = new Compiler.Local(ctx, itemType))
{
ctx.LoadAddress(list, ExpectedType);
ctx.EmitCall(getEnumerator, ExpectedType);
ctx.StoreValue(iter);
using (ctx.Using(iter))
{
Compiler.CodeLabel body = ctx.DefineLabel(), next = ctx.DefineLabel();
ctx.Branch(next, false);
ctx.MarkLabel(body);
ctx.LoadAddress(iter, enumeratorType);
ctx.EmitCall(current, enumeratorType);
if (itemType != ctx.MapType(typeof(object)) && current.ReturnType == ctx.MapType(typeof(object)))
{
ctx.CastFromObject(itemType);
}
ctx.StoreValue(kvp);
ctx.LoadValue(fieldNumber);
ctx.LoadValue((int)wireType);
ctx.LoadReaderWriter();
ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("WriteFieldHeader"));
ctx.LoadNullRef();
ctx.LoadReaderWriter();
ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("StartSubItem"));
ctx.StoreValue(token);
ctx.LoadAddress(kvp, itemType);
ctx.EmitCall(key, itemType);
ctx.WriteNullCheckedTail(typeof(TKey), keyTail, null);
ctx.LoadAddress(kvp, itemType);
ctx.EmitCall(value, itemType);
ctx.WriteNullCheckedTail(typeof(TValue), Tail, null);
ctx.LoadValue(token);
ctx.LoadReaderWriter();
ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("EndSubItem"));
ctx.MarkLabel(@next);
ctx.LoadAddress(iter, enumeratorType);
ctx.EmitCall(moveNext, enumeratorType);
ctx.BranchIfTrue(body, false);
}
}
}
protected override void EmitRead(CompilerContext ctx, Local valueFrom)
{
using (Compiler.Local list = AppendToCollection ? ctx.GetLocalWithValue(ExpectedType, valueFrom)
: new Compiler.Local(ctx, typeof(TDictionary)))
using (Compiler.Local token = new Compiler.Local(ctx, typeof(SubItemToken)))
using (Compiler.Local key = new Compiler.Local(ctx, typeof(TKey)))
using (Compiler.Local @value = new Compiler.Local(ctx, typeof(TValue)))
using (Compiler.Local fieldNumber = new Compiler.Local(ctx, ctx.MapType(typeof(int))))
{
if (!AppendToCollection)
{ // always new
ctx.LoadNullRef();
ctx.StoreValue(list);
}
if (concreteType != null)
{
ctx.LoadValue(list);
Compiler.CodeLabel notNull = ctx.DefineLabel();
ctx.BranchIfTrue(notNull, true);
ctx.EmitCtor(concreteType);
ctx.StoreValue(list);
ctx.MarkLabel(notNull);
}
var redoFromStart = ctx.DefineLabel();
ctx.MarkLabel(redoFromStart);
// key = default(TKey); value = default(TValue);
if (typeof(TKey) == typeof(string))
{
ctx.LoadValue("");
ctx.StoreValue(key);
}
else
{
ctx.InitLocal(typeof(TKey), key);
}
if (typeof(TValue) == typeof(string))
{
ctx.LoadValue("");
ctx.StoreValue(value);
}
else
{
ctx.InitLocal(typeof(TValue), @value);
}
// token = ProtoReader.StartSubItem(reader);
ctx.LoadReaderWriter();
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("StartSubItem"));
ctx.StoreValue(token);
Compiler.CodeLabel @continue = ctx.DefineLabel(), processField = ctx.DefineLabel();
// while ...
ctx.Branch(@continue, false);
// switch(fieldNumber)
ctx.MarkLabel(processField);
ctx.LoadValue(fieldNumber);
CodeLabel @default = ctx.DefineLabel(), one = ctx.DefineLabel(), two = ctx.DefineLabel();
ctx.Switch(new[] { @default, one, two }); // zero based, hence explicit 0
// case 0: default: reader.SkipField();
ctx.MarkLabel(@default);
ctx.LoadReaderWriter();
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("SkipField"));
ctx.Branch(@continue, false);
// case 1: key = ...
ctx.MarkLabel(one);
keyTail.EmitRead(ctx, null);
ctx.StoreValue(key);
ctx.Branch(@continue, false);
// case 2: value = ...
ctx.MarkLabel(two);
Tail.EmitRead(ctx, Tail.RequiresOldValue ? @value : null);
ctx.StoreValue(value);
// (fieldNumber = reader.ReadFieldHeader()) > 0
ctx.MarkLabel(@continue);
ctx.EmitBasicRead("ReadFieldHeader", ctx.MapType(typeof(int)));
ctx.CopyValue();
ctx.StoreValue(fieldNumber);
ctx.LoadValue(0);
ctx.BranchIfGreater(processField, false);
// ProtoReader.EndSubItem(token, reader);
ctx.LoadValue(token);
ctx.LoadReaderWriter();
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("EndSubItem"));
// list[key] = value;
ctx.LoadAddress(list, ExpectedType);
ctx.LoadValue(key);
ctx.LoadValue(@value);
ctx.EmitCall(indexerSet);
// while reader.TryReadFieldReader(fieldNumber)
ctx.LoadReaderWriter();
ctx.LoadValue(this.fieldNumber);
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("TryReadFieldHeader"));
ctx.BranchIfTrue(redoFromStart, false);
if (ReturnsValue)
{
ctx.LoadValue(list);
}
}
}
#endif
}
}