44using System . Linq ;
55using System . Linq . Expressions ;
66using System . Reflection ;
7+ using AustinHarris . JsonRpc . Jsmn ;
78using AustinHarris . JsonRpc . Serialization ;
89
910namespace AustinHarris . JsonRpc . Invocation
@@ -18,13 +19,29 @@ namespace AustinHarris.JsonRpc.Invocation
1819 /// <summary>Same binding, but returns the boxed result. Used when pre/post handlers need a <see cref="JsonResponse"/>.</summary>
1920 public delegate object BoxedInvoker ( JsonRpcRequestReader reader , int [ ] map ) ;
2021
22+ /// <summary>
23+ /// The invoker specialised for the built-in serializer: parameters are read from the tokens by direct static
24+ /// calls and the result is formatted into the concrete pooled writer, so a primitive request goes from bytes to
25+ /// bytes without a virtual, delegate or interface call around the service method.
26+ /// </summary>
27+ internal delegate void JsmnInvoker ( JsmnRequestReader reader , int [ ] map , PooledByteBufferWriter output ) ;
28+
2129 public sealed class RpcParameter
2230 {
23- public string Name { get ; internal set ; }
24- public Type Type { get ; internal set ; }
25- public bool HasDefault { get ; internal set ; }
26- public object DefaultValue { get ; internal set ; }
27- internal byte [ ] NameUtf8 ;
31+ internal RpcParameter ( string name , Type type , bool hasDefault , object defaultValue )
32+ {
33+ Name = name ;
34+ Type = type ;
35+ HasDefault = hasDefault ;
36+ DefaultValue = defaultValue ;
37+ NameUtf8 = System . Text . Encoding . UTF8 . GetBytes ( name ) ;
38+ }
39+
40+ public string Name { get ; }
41+ public Type Type { get ; }
42+ public bool HasDefault { get ; }
43+ public object DefaultValue { get ; }
44+ internal readonly byte [ ] NameUtf8 ;
2845 }
2946
3047 /// <summary>
@@ -40,8 +57,11 @@ public sealed class RpcMethod
4057 public int DefaultCount { get ; private set ; }
4158 public StreamingInvoker Invoke { get ; private set ; }
4259 public BoxedInvoker InvokeBoxed { get ; private set ; }
60+ internal JsmnInvoker InvokeJsmn { get ; private set ; }
4361 /// <summary>The identity map (0,1,2,...) used when positional params match exactly.</summary>
4462 internal int [ ] IdentityMap { get ; private set ; }
63+ /// <summary>True when no two parameters share a JSON name (named params in declaration order can then use <see cref="IdentityMap"/>).</summary>
64+ internal bool HasUniqueNames { get ; private set ; }
4565
4666 private static readonly MethodInfo ReadParamGeneric = typeof ( JsonRpcRequestReader )
4767 . GetMethods ( BindingFlags . Public | BindingFlags . Instance )
@@ -51,7 +71,34 @@ public sealed class RpcMethod
5171 . GetMethods ( BindingFlags . Public | BindingFlags . Instance )
5272 . First ( m => m . Name == nameof ( JsonRpcSerializer . Write ) && m . IsGenericMethodDefinition ) ;
5373
54- private static readonly MethodInfo WriteNullMethod = typeof ( Utf8Json ) . GetMethod ( nameof ( Utf8Json . WriteNull ) ) ;
74+ private static readonly MethodInfo WriteNullMethod = typeof ( Utf8Json ) . GetMethod ( nameof ( Utf8Json . WriteNull ) , new [ ] { typeof ( IBufferWriter < byte > ) } ) ;
75+ private static readonly MethodInfo WriteNullPooled = typeof ( Utf8Json ) . GetMethod ( nameof ( Utf8Json . WriteNull ) , new [ ] { typeof ( PooledByteBufferWriter ) } ) ;
76+ private static readonly MethodInfo ReadTypedParamGeneric = typeof ( JsmnRequestReader ) . GetMethod ( nameof ( JsmnRequestReader . ReadTypedParam ) , BindingFlags . NonPublic | BindingFlags . Static ) ;
77+ private static readonly MethodInfo ParamIsNullLiteral = typeof ( JsmnRequestReader ) . GetMethod ( nameof ( JsmnRequestReader . ParamIsNullLiteral ) , BindingFlags . NonPublic | BindingFlags . Static ) ;
78+ private static readonly MethodInfo WriteValuePooledGeneric = typeof ( RpcMethod ) . GetMethod ( nameof ( WriteValuePooled ) , BindingFlags . NonPublic | BindingFlags . Static ) ;
79+
80+ /// <summary>The built-in typed writer for a non-primitive result (POCO, collection, nullable of a non-primitive).</summary>
81+ private static void WriteValuePooled < T > ( PooledByteBufferWriter output , T value ) => JsmnWriter < T > . Write ( output , value ) ;
82+
83+ // primitive type -> (static reader on JsmnRequestReader, static writer on Utf8Json taking the pooled writer)
84+ private static readonly Dictionary < Type , ( MethodInfo Read , MethodInfo Write ) > Primitives = BuildPrimitiveTable ( ) ;
85+
86+ private static Dictionary < Type , ( MethodInfo , MethodInfo ) > BuildPrimitiveTable ( )
87+ {
88+ var reader = typeof ( JsmnRequestReader ) ;
89+ MethodInfo Read ( string name ) => reader . GetMethod ( name , BindingFlags . NonPublic | BindingFlags . Static ) ;
90+ MethodInfo Write ( string name , Type t ) => typeof ( Utf8Json ) . GetMethod ( name , new [ ] { typeof ( PooledByteBufferWriter ) , t } ) ;
91+ return new Dictionary < Type , ( MethodInfo , MethodInfo ) >
92+ {
93+ [ typeof ( string ) ] = ( Read ( nameof ( JsmnRequestReader . ReadStringParam ) ) , Write ( nameof ( Utf8Json . WriteString ) , typeof ( string ) ) ) ,
94+ [ typeof ( int ) ] = ( Read ( nameof ( JsmnRequestReader . ReadInt32Param ) ) , Write ( nameof ( Utf8Json . WriteInt64 ) , typeof ( long ) ) ) ,
95+ [ typeof ( long ) ] = ( Read ( nameof ( JsmnRequestReader . ReadInt64Param ) ) , Write ( nameof ( Utf8Json . WriteInt64 ) , typeof ( long ) ) ) ,
96+ [ typeof ( double ) ] = ( Read ( nameof ( JsmnRequestReader . ReadDoubleParam ) ) , Write ( nameof ( Utf8Json . WriteDouble ) , typeof ( double ) ) ) ,
97+ [ typeof ( float ) ] = ( Read ( nameof ( JsmnRequestReader . ReadSingleParam ) ) , Write ( nameof ( Utf8Json . WriteSingle ) , typeof ( float ) ) ) ,
98+ [ typeof ( bool ) ] = ( Read ( nameof ( JsmnRequestReader . ReadBooleanParam ) ) , Write ( nameof ( Utf8Json . WriteBool ) , typeof ( bool ) ) ) ,
99+ [ typeof ( decimal ) ] = ( Read ( nameof ( JsmnRequestReader . ReadDecimalParam ) ) , Write ( nameof ( Utf8Json . WriteDecimal ) , typeof ( decimal ) ) ) ,
100+ } ;
101+ }
55102
56103 /// <summary>Builds the invokers for an instance (or static) method; <paramref name="parameterNames"/> are the JSON names (null = CLR names).</summary>
57104 public static RpcMethod FromMethod ( string name , MethodInfo method , object target , string [ ] parameterNames = null )
@@ -104,8 +151,13 @@ private static RpcMethod Build(string name, ParameterInfo[] ps, Type returnType,
104151 bool expectsRef = ps . Length > 0 && ps [ ps . Length - 1 ] . ParameterType == typeof ( JsonRpcException ) . MakeByRefType ( ) ;
105152 int bindable = expectsRef ? ps . Length - 1 : ps . Length ;
106153
154+ // the built-in invoker binds the same parameters through direct static reads
155+ var jsmnReader = Expression . Parameter ( typeof ( JsmnRequestReader ) , "reader" ) ;
156+ var pooled = Expression . Parameter ( typeof ( PooledByteBufferWriter ) , "output" ) ;
157+
107158 var parameters = new RpcParameter [ bindable ] ;
108159 var args = new Expression [ ps . Length ] ;
160+ var jsmnArgs = new Expression [ ps . Length ] ;
109161 int defaultCount = 0 ;
110162 for ( int i = 0 ; i < bindable ; i ++ )
111163 {
@@ -117,59 +169,63 @@ private static RpcMethod Build(string name, ParameterInfo[] ps, Type returnType,
117169 else if ( p . IsOptional && p . DefaultValue != DBNull . Value && p . DefaultValue != Type . Missing ) defaultValue = p . DefaultValue ;
118170 if ( hasDefault ) defaultCount ++ ;
119171
120- parameters [ i ] = new RpcParameter
121- {
122- Name = jsonName ,
123- Type = p . ParameterType ,
124- HasDefault = hasDefault ,
125- DefaultValue = defaultValue ,
126- NameUtf8 = System . Text . Encoding . UTF8 . GetBytes ( jsonName )
127- } ;
172+ parameters [ i ] = new RpcParameter ( jsonName , p . ParameterType , hasDefault , defaultValue ) ;
128173
129174 var index = Expression . ArrayIndex ( map , Expression . Constant ( i ) ) ;
130175 var read = Expression . Call ( reader , ReadParamGeneric . MakeGenericMethod ( p . ParameterType ) , index ) ;
131176 Expression fallback = MakeDefault ( p . ParameterType , hasDefault , defaultValue ) ;
132- args [ i ] = Expression . Condition ( Expression . GreaterThanOrEqual ( index , Expression . Constant ( 0 ) ) , read , fallback ) ;
177+ var supplied = Expression . GreaterThanOrEqual ( index , Expression . Constant ( 0 ) ) ;
178+ args [ i ] = Expression . Condition ( supplied , read , fallback ) ;
179+ jsmnArgs [ i ] = Expression . Condition ( supplied , MakeJsmnRead ( jsmnReader , index , p . ParameterType ) , fallback ) ;
133180 }
134- if ( expectsRef ) args [ ps . Length - 1 ] = refEx ;
181+ if ( expectsRef ) args [ ps . Length - 1 ] = jsmnArgs [ ps . Length - 1 ] = refEx ;
135182
136183 var call = makeCall ( args ) ;
184+ var jsmnCall = makeCall ( jsmnArgs ) ;
137185 var throwIfRef = expectsRef
138186 ? ( Expression ) Expression . IfThen ( Expression . NotEqual ( refEx , Expression . Constant ( null , typeof ( JsonRpcException ) ) ) , Expression . Throw ( refEx ) )
139187 : Expression . Empty ( ) ;
188+ var clearRef = Expression . Assign ( refEx , Expression . Constant ( null , typeof ( JsonRpcException ) ) ) ;
140189
141190 // streaming: result = call(); if (refEx != null) throw; serializer.Write<TRet>(output, result)
142191 Expression streamingBody ;
143192 Expression boxedBody ;
193+ Expression jsmnBody ;
144194 if ( returnType == typeof ( void ) )
145195 {
146- streamingBody = Expression . Block ( new [ ] { refEx } ,
147- Expression . Assign ( refEx , Expression . Constant ( null , typeof ( JsonRpcException ) ) ) ,
148- call , throwIfRef , Expression . Call ( WriteNullMethod , output ) ) ;
149- boxedBody = Expression . Block ( typeof ( object ) , new [ ] { refEx } ,
150- Expression . Assign ( refEx , Expression . Constant ( null , typeof ( JsonRpcException ) ) ) ,
151- call , throwIfRef , Expression . Constant ( null , typeof ( object ) ) ) ;
196+ streamingBody = Expression . Block ( new [ ] { refEx } , clearRef , call , throwIfRef , Expression . Call ( WriteNullMethod , output ) ) ;
197+ boxedBody = Expression . Block ( typeof ( object ) , new [ ] { refEx } , clearRef , call , throwIfRef , Expression . Constant ( null , typeof ( object ) ) ) ;
198+ jsmnBody = Expression . Block ( new [ ] { refEx } , clearRef , jsmnCall , throwIfRef , Expression . Call ( WriteNullPooled , pooled ) ) ;
152199 }
153200 else
154201 {
155202 var result = Expression . Variable ( returnType , "result" ) ;
156203 streamingBody = Expression . Block ( new [ ] { refEx , result } ,
157- Expression . Assign ( refEx , Expression . Constant ( null , typeof ( JsonRpcException ) ) ) ,
204+ clearRef ,
158205 Expression . Assign ( result , call ) ,
159206 throwIfRef ,
160207 Expression . Call ( serializer , WriteGeneric . MakeGenericMethod ( returnType ) , output , result ) ) ;
161208 boxedBody = Expression . Block ( typeof ( object ) , new [ ] { refEx , result } ,
162- Expression . Assign ( refEx , Expression . Constant ( null , typeof ( JsonRpcException ) ) ) ,
209+ clearRef ,
163210 Expression . Assign ( result , call ) ,
164211 throwIfRef ,
165212 Expression . Convert ( result , typeof ( object ) ) ) ;
213+ jsmnBody = Expression . Block ( new [ ] { refEx , result } ,
214+ clearRef ,
215+ Expression . Assign ( result , jsmnCall ) ,
216+ throwIfRef ,
217+ MakeJsmnWrite ( pooled , result , returnType ) ) ;
166218 }
167219
168220 var streaming = Expression . Lambda < StreamingInvoker > ( streamingBody , reader , map , serializer , output ) . Compile ( ) ;
169221 var boxed = Expression . Lambda < BoxedInvoker > ( boxedBody , reader , map ) . Compile ( ) ;
222+ var jsmnInvoker = Expression . Lambda < JsmnInvoker > ( jsmnBody , jsmnReader , map , pooled ) . Compile ( ) ;
170223
171224 var identity = new int [ bindable ] ;
172225 for ( int i = 0 ; i < bindable ; i ++ ) identity [ i ] = i ;
226+ var names = new HashSet < string > ( ) ;
227+ bool unique = true ;
228+ foreach ( var parameter in parameters ) unique &= names . Add ( parameter . Name ) ;
173229
174230 return new RpcMethod
175231 {
@@ -180,10 +236,52 @@ private static RpcMethod Build(string name, ParameterInfo[] ps, Type returnType,
180236 DefaultCount = defaultCount ,
181237 Invoke = streaming ,
182238 InvokeBoxed = boxed ,
183- IdentityMap = identity
239+ InvokeJsmn = jsmnInvoker ,
240+ IdentityMap = identity ,
241+ HasUniqueNames = unique
184242 } ;
185243 }
186244
245+ /// <summary>
246+ /// The built-in read of parameter <paramref name="index"/> as <paramref name="type"/>: a direct static call
247+ /// for the primitives, a null test plus the value read for their nullables, the typed reader cache for the rest.
248+ /// </summary>
249+ private static Expression MakeJsmnRead ( ParameterExpression reader , Expression index , Type type )
250+ {
251+ if ( Primitives . TryGetValue ( type , out var primitive ) ) return Expression . Call ( primitive . Read , reader , index ) ;
252+ var underlying = Nullable . GetUnderlyingType ( type ) ;
253+ if ( underlying != null && Primitives . TryGetValue ( underlying , out var inner ) )
254+ {
255+ return Expression . Condition (
256+ Expression . Call ( ParamIsNullLiteral , reader , index ) ,
257+ Expression . Constant ( null , type ) ,
258+ Expression . Convert ( Expression . Call ( inner . Read , reader , index ) , type ) ) ;
259+ }
260+ return Expression . Call ( ReadTypedParamGeneric . MakeGenericMethod ( type ) , reader , index ) ;
261+ }
262+
263+ /// <summary>The built-in write of <paramref name="result"/>: the concrete-writer formatter for primitives (and their nullables), the typed writer cache otherwise.</summary>
264+ private static Expression MakeJsmnWrite ( ParameterExpression output , ParameterExpression result , Type type )
265+ {
266+ if ( Primitives . TryGetValue ( type , out var primitive ) )
267+ {
268+ Expression value = result ;
269+ if ( type == typeof ( int ) ) value = Expression . Convert ( result , typeof ( long ) ) ;
270+ return Expression . Call ( primitive . Write , output , value ) ;
271+ }
272+ var underlying = Nullable . GetUnderlyingType ( type ) ;
273+ if ( underlying != null && Primitives . TryGetValue ( underlying , out var inner ) )
274+ {
275+ Expression value = Expression . Property ( result , "Value" ) ;
276+ if ( underlying == typeof ( int ) ) value = Expression . Convert ( value , typeof ( long ) ) ;
277+ return Expression . IfThenElse (
278+ Expression . Property ( result , "HasValue" ) ,
279+ Expression . Call ( inner . Write , output , value ) ,
280+ Expression . Call ( WriteNullPooled , output ) ) ;
281+ }
282+ return Expression . Call ( WriteValuePooledGeneric . MakeGenericMethod ( type ) , output , result ) ;
283+ }
284+
187285 private static Expression MakeDefault ( Type type , bool hasDefault , object value )
188286 {
189287 if ( ! hasDefault || value == null ) return Expression . Default ( type ) ;
0 commit comments