Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit d902069

Browse files
committed
Refactor AOT classes and move to drop-in JsAot
1 parent 9c1467e commit d902069

File tree

9 files changed

+344
-276
lines changed

9 files changed

+344
-276
lines changed

src/ServiceStack.Text/Common/DeserializeCollection.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ public static object ParseCollectionType(string value, Type createType, Type ele
9696

9797
public static object ParseCollectionType(StringSegment value, Type createType, Type elementType, ParseStringSegmentDelegate parseFn)
9898
{
99-
ParseCollectionDelegate parseDelegate;
100-
if (ParseDelegateCache.TryGetValue(elementType, out parseDelegate))
99+
if (ParseDelegateCache.TryGetValue(elementType, out var parseDelegate))
101100
return parseDelegate(value, createType, parseFn);
102101

103102
var mi = typeof(DeserializeCollection<TSerializer>).GetStaticMethod("ParseCollection",

src/ServiceStack.Text/Common/JsReader.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Reflection;
5+
using System.Runtime.CompilerServices;
56
#if NETSTANDARD2_0
67
using Microsoft.Extensions.Primitives;
78
#else
@@ -151,5 +152,15 @@ private ParseStringSegmentDelegate GetCoreParseStringSegmentFn<T>()
151152
return DeserializeType<TSerializer>.ParseAbstractType<T>;
152153
}
153154

155+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
156+
public static void InitAot<T>()
157+
{
158+
var hold = DeserializeBuiltin<T>.Parse;
159+
hold = DeserializeArray<T[], TSerializer>.Parse;
160+
DeserializeType<TSerializer>.ExtractType(null);
161+
DeserializeArrayWithElements<T, TSerializer>.ParseGenericArray(null, null);
162+
DeserializeCollection<TSerializer>.ParseCollection<T>(null, null, null);
163+
DeserializeListWithElements<T, TSerializer>.ParseGenericList(null, null, null);
164+
}
154165
}
155166
}

src/ServiceStack.Text/Common/JsWriter.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.IO;
5+
using System.Runtime.CompilerServices;
56
using ServiceStack.Text.Json;
67
using ServiceStack.Text.Jsv;
78

@@ -431,8 +432,7 @@ private WriteObjectDelegate GetCoreWriteFn<T>()
431432

432433
public WriteObjectDelegate GetSpecialWriteFn(Type type)
433434
{
434-
WriteObjectDelegate writeFn = null;
435-
if (SpecialTypes.TryGetValue(type, out writeFn))
435+
if (SpecialTypes.TryGetValue(type, out var writeFn))
436436
return writeFn;
437437

438438
if (type.IsInstanceOfType(typeof(Type)))
@@ -448,5 +448,22 @@ public void WriteType(TextWriter writer, object value)
448448
{
449449
Serializer.WriteRawString(writer, JsConfig.TypeWriter((Type)value));
450450
}
451+
452+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
453+
public static void InitAot<T>()
454+
{
455+
WriteListsOfElements<T, TSerializer>.WriteList(null, null);
456+
WriteListsOfElements<T, TSerializer>.WriteIList(null, null);
457+
WriteListsOfElements<T, TSerializer>.WriteEnumerable(null, null);
458+
WriteListsOfElements<T, TSerializer>.WriteListValueType(null, null);
459+
WriteListsOfElements<T, TSerializer>.WriteIListValueType(null, null);
460+
WriteListsOfElements<T, TSerializer>.WriteGenericArrayValueType(null, null);
461+
WriteListsOfElements<T, TSerializer>.WriteArray(null, null);
462+
463+
TranslateListWithElements<T>.LateBoundTranslateToGenericICollection(null, null);
464+
TranslateListWithConvertibleElements<T, T>.LateBoundTranslateToGenericICollection(null, null);
465+
466+
QueryStringWriter<T>.WriteObject(null, null);
467+
}
451468
}
452469
}

src/ServiceStack.Text/JsAot.cs

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
#if __IOS__ || __ANDROID__ || WINDOWS_UWP || __MOBILE__
2+
3+
// When Linking "SDK and User Assemblies" in Xamarin you can copy this class to your project and call `JsAot.Run()` on Startup
4+
// Alternative solution is to add 'ServiceStack.Text' to your "Skip linking assemblies" list which should contain:
5+
// ServiceStack.Text;ServiceStack.Client;{Your}.ServiceModel
6+
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using System.Runtime.CompilerServices;
11+
using System.Text;
12+
using System.Threading.Tasks;
13+
using ServiceStack.Text;
14+
using ServiceStack.Text.Common;
15+
using Xamarin.Forms.Internals;
16+
17+
namespace ServiceStack
18+
{
19+
public static class JsAot
20+
{
21+
[Preserve]
22+
public static void Init() {}
23+
24+
/// <summary>
25+
/// Provide hint to IOS AOT compiler to pre-compile generic classes for all your DTOs.
26+
/// Just needs to be called once in a static constructor.
27+
/// </summary>
28+
[Preserve]
29+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
30+
public static void Run()
31+
{
32+
try
33+
{
34+
RegisterForAot();
35+
}
36+
catch (Exception ex)
37+
{
38+
Console.WriteLine(ex);
39+
throw;
40+
}
41+
}
42+
43+
[Preserve(AllMembers = true)]
44+
internal class Poco
45+
{
46+
public string Dummy { get; set; }
47+
}
48+
49+
[Preserve]
50+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
51+
internal static void RegisterForAot()
52+
{
53+
RegisterTypeForAot<Poco>();
54+
55+
RegisterElement<Poco, string>();
56+
57+
RegisterElement<Poco, bool>();
58+
RegisterElement<Poco, char>();
59+
RegisterElement<Poco, byte>();
60+
RegisterElement<Poco, sbyte>();
61+
RegisterElement<Poco, short>();
62+
RegisterElement<Poco, ushort>();
63+
RegisterElement<Poco, int>();
64+
RegisterElement<Poco, uint>();
65+
66+
RegisterElement<Poco, long>();
67+
RegisterElement<Poco, ulong>();
68+
RegisterElement<Poco, float>();
69+
RegisterElement<Poco, double>();
70+
RegisterElement<Poco, decimal>();
71+
72+
RegisterElement<Poco, bool?>();
73+
RegisterElement<Poco, char?>();
74+
RegisterElement<Poco, byte?>();
75+
RegisterElement<Poco, sbyte?>();
76+
RegisterElement<Poco, short?>();
77+
RegisterElement<Poco, ushort?>();
78+
RegisterElement<Poco, int?>();
79+
RegisterElement<Poco, uint?>();
80+
RegisterElement<Poco, long?>();
81+
RegisterElement<Poco, ulong?>();
82+
RegisterElement<Poco, float?>();
83+
RegisterElement<Poco, double?>();
84+
RegisterElement<Poco, decimal?>();
85+
86+
RegisterElement<Poco, JsonValue>();
87+
RegisterTypeForAot<DayOfWeek>(); // used by DateTime
88+
89+
// register built in structs
90+
RegisterTypeForAot<Guid>();
91+
RegisterTypeForAot<TimeSpan>();
92+
RegisterTypeForAot<DateTime>();
93+
RegisterTypeForAot<DateTimeOffset>();
94+
95+
RegisterTypeForAot<Guid?>();
96+
RegisterTypeForAot<TimeSpan?>();
97+
RegisterTypeForAot<DateTime?>();
98+
RegisterTypeForAot<DateTimeOffset?>();
99+
}
100+
101+
[Preserve]
102+
public static void RegisterTypeForAot<T>()
103+
{
104+
AotConfig.RegisterSerializers<T>();
105+
}
106+
107+
[Preserve]
108+
public static void RegisterQueryStringWriter()
109+
{
110+
var i = 0;
111+
if (QueryStringWriter<Poco>.WriteFn() != null) i++;
112+
}
113+
114+
[Preserve]
115+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
116+
public static int RegisterElement<T, TElement>()
117+
{
118+
var i = 0;
119+
AotConfig.RegisterSerializers<TElement>();
120+
AotConfig.RegisterElement<T, TElement, Text.Json.JsonTypeSerializer>();
121+
AotConfig.RegisterElement<T, TElement, Text.Jsv.JsvTypeSerializer>();
122+
return i;
123+
}
124+
125+
[Preserve(AllMembers = true)]
126+
internal class AotConfig
127+
{
128+
internal static JsReader<Text.Json.JsonTypeSerializer> jsonReader;
129+
internal static JsWriter<Text.Json.JsonTypeSerializer> jsonWriter;
130+
internal static JsReader<Text.Jsv.JsvTypeSerializer> jsvReader;
131+
internal static JsWriter<Text.Jsv.JsvTypeSerializer> jsvWriter;
132+
internal static Text.Json.JsonTypeSerializer jsonSerializer;
133+
internal static Text.Jsv.JsvTypeSerializer jsvSerializer;
134+
135+
[Preserve]
136+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
137+
static AotConfig()
138+
{
139+
jsonSerializer = new Text.Json.JsonTypeSerializer();
140+
jsvSerializer = new Text.Jsv.JsvTypeSerializer();
141+
jsonReader = new JsReader<Text.Json.JsonTypeSerializer>();
142+
jsonWriter = new JsWriter<Text.Json.JsonTypeSerializer>();
143+
jsvReader = new JsReader<Text.Jsv.JsvTypeSerializer>();
144+
jsvWriter = new JsWriter<Text.Jsv.JsvTypeSerializer>();
145+
}
146+
147+
[Preserve]
148+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
149+
internal static void RegisterSerializers<T>()
150+
{
151+
var i = 0;
152+
Register<T, Text.Json.JsonTypeSerializer>();
153+
jsonSerializer.GetParseFn<T>();
154+
jsonSerializer.GetWriteFn<T>();
155+
jsonReader.GetParseFn<T>();
156+
jsonWriter.GetWriteFn<T>();
157+
158+
Register<T, Text.Jsv.JsvTypeSerializer>();
159+
jsvSerializer.GetParseFn<T>();
160+
jsvSerializer.GetWriteFn<T>();
161+
jsvReader.GetParseFn<T>();
162+
jsvWriter.GetWriteFn<T>();
163+
164+
RegisterCsvSerializer<T>();
165+
RegisterQueryStringWriter();
166+
}
167+
168+
[Preserve]
169+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
170+
internal static void RegisterCsvSerializer<T>()
171+
{
172+
CsvSerializer<T>.WriteFn();
173+
CsvSerializer<T>.WriteObject(null, null);
174+
CsvWriter<T>.Write(null, default(IEnumerable<T>));
175+
CsvWriter<T>.WriteRow(null, default(T));
176+
}
177+
178+
[Preserve]
179+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
180+
public static ParseStringDelegate GetParseFn(Type type)
181+
{
182+
var parseFn = Text.Json.JsonTypeSerializer.Instance.GetParseFn(type);
183+
return parseFn;
184+
}
185+
186+
[Preserve]
187+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
188+
internal static void Register<T, TSerializer>() where TSerializer : ITypeSerializer
189+
{
190+
var i = 0;
191+
192+
Text.Json.JsonReader.InitAot<T>();
193+
Text.Json.JsonWriter.InitAot<T>();
194+
195+
Text.Jsv.JsvReader.InitAot<T>();
196+
Text.Jsv.JsvWriter.InitAot<T>();
197+
198+
var hold = new object[]
199+
{
200+
new List<T>(),
201+
new T[0],
202+
new Dictionary<string, string>(),
203+
new Dictionary<string, T>(),
204+
new HashSet<T>(),
205+
};
206+
207+
JsConfig<T>.ExcludeTypeInfo = false;
208+
209+
if (JsConfig<T>.OnDeserializedFn != null) i++;
210+
if (JsConfig<T>.HasDeserializeFn) i++;
211+
if (JsConfig<T>.SerializeFn != null) i++;
212+
if (JsConfig<T>.DeSerializeFn != null) i++;
213+
if (TypeConfig<T>.Properties != null) i++;
214+
215+
JsReader<TSerializer>.InitAot<T>();
216+
JsWriter<TSerializer>.InitAot<T>();
217+
}
218+
219+
[Preserve]
220+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
221+
internal static void RegisterElement<T, TElement, TSerializer>() where TSerializer : ITypeSerializer
222+
{
223+
DeserializeDictionary<TSerializer>.ParseDictionary<T, TElement>(null, null, null, null);
224+
DeserializeDictionary<TSerializer>.ParseDictionary<TElement, T>(null, null, null, null);
225+
226+
ToStringDictionaryMethods<T, TElement, TSerializer>.WriteIDictionary(null, null, null, null);
227+
ToStringDictionaryMethods<TElement, T, TSerializer>.WriteIDictionary(null, null, null, null);
228+
229+
// Include List deserialisations from the Register<> method above. This solves issue where List<Guid> properties on responses deserialise to null.
230+
// No idea why this is happening because there is no visible exception raised. Suspect IOS is swallowing an AOT exception somewhere.
231+
DeserializeArrayWithElements<TElement, TSerializer>.ParseGenericArray(null, null);
232+
DeserializeListWithElements<TElement, TSerializer>.ParseGenericList(null, null, null);
233+
234+
// Cannot use the line below for some unknown reason - when trying to compile to run on device, mtouch bombs during native code compile.
235+
// Something about this line or its inner workings is offensive to mtouch. Luckily this was not needed for my List<Guide> issue.
236+
// DeserializeCollection<JsonTypeSerializer>.ParseCollection<TElement>(null, null, null);
237+
238+
TranslateListWithElements<TElement>.LateBoundTranslateToGenericICollection(null, typeof(List<TElement>));
239+
TranslateListWithConvertibleElements<TElement, TElement>.LateBoundTranslateToGenericICollection(null, typeof(List<TElement>));
240+
}
241+
}
242+
}
243+
244+
}
245+
246+
#endif

src/ServiceStack.Text/Json/JsonReader.Generic.cs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Runtime.CompilerServices;
67
using System.Threading;
78
using ServiceStack.Text.Common;
89
#if NETSTANDARD2_0
@@ -22,10 +23,10 @@ public static class JsonReader
2223

2324
internal static ParseStringSegmentDelegate GetParseStringSegmentFn(Type type)
2425
{
25-
ParseFactoryDelegate parseFactoryFn;
26-
ParseFnCache.TryGetValue(type, out parseFactoryFn);
26+
ParseFnCache.TryGetValue(type, out var parseFactoryFn);
2727

28-
if (parseFactoryFn != null) return parseFactoryFn();
28+
if (parseFactoryFn != null)
29+
return parseFactoryFn();
2930

3031
var genericType = typeof(JsonReader<>).MakeGenericType(type);
3132
var mi = genericType.GetStaticMethod("GetParseStringSegmentFn");
@@ -35,17 +36,28 @@ internal static ParseStringSegmentDelegate GetParseStringSegmentFn(Type type)
3536
do
3637
{
3738
snapshot = ParseFnCache;
38-
newCache = new Dictionary<Type, ParseFactoryDelegate>(ParseFnCache);
39-
newCache[type] = parseFactoryFn;
39+
newCache = new Dictionary<Type, ParseFactoryDelegate>(ParseFnCache)
40+
{
41+
[type] = parseFactoryFn
42+
};
4043

4144
} while (!ReferenceEquals(
4245
Interlocked.CompareExchange(ref ParseFnCache, newCache, snapshot), snapshot));
4346

4447
return parseFactoryFn();
4548
}
49+
50+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
51+
public static void InitAot<T>()
52+
{
53+
Text.Json.JsonReader.Instance.GetParseFn<T>();
54+
Text.Json.JsonReader<T>.Parse(null);
55+
Text.Json.JsonReader<T>.GetParseFn();
56+
Text.Json.JsonReader<T>.GetParseStringSegmentFn();
57+
}
4658
}
4759

48-
public static class JsonReader<T>
60+
internal static class JsonReader<T>
4961
{
5062
private static ParseStringSegmentDelegate ReadFn;
5163

@@ -64,14 +76,15 @@ public static void Refresh()
6476
ReadFn = JsonReader.Instance.GetParseStringSegmentFn<T>();
6577
}
6678

67-
public static ParseStringDelegate GetParseFn() =>
68-
ReadFn != null
79+
public static ParseStringDelegate GetParseFn() => ReadFn != null
6980
? (ParseStringDelegate)(v => ReadFn(new StringSegment(v)))
7081
: Parse;
7182

7283
public static ParseStringSegmentDelegate GetParseStringSegmentFn() => ReadFn ?? Parse;
7384

74-
public static object Parse(string value) => Parse(new StringSegment(value));
85+
public static object Parse(string value) => value != null
86+
? Parse(new StringSegment(value))
87+
: null;
7588

7689
public static object Parse(StringSegment value)
7790
{

0 commit comments

Comments
 (0)