Skip to content

Commit

Permalink
Defer dynamic export of type members until first access (#192)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasongin authored Jan 26, 2024
1 parent 3eb943b commit a9b983a
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 153 deletions.
16 changes: 12 additions & 4 deletions README-DEV.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,20 @@ with no CLR.
## Debugging
With a debug build, the following environment variables trigger just-in-time debugging of the
respective components:
- `DEBUG_NODE_API_GENERATOR` - Debug the C# source-generator when it runs during the build.
- `DEBUG_NODE_API_RUNTIME` - Debug the .NET runtime host when it is loaded by JavaScript. (Does
- `NODE_API_DEBUG_GENERATOR=1` - Debug the C# source-generator or TS type-definitions generator
when they runs during the build.
- `NODE_API_DEBUG_RUNTIME=1` - Debug the .NET runtime host when it is loaded by JavaScript. (Does
not apply to AOT-compiled modules.)
Setting either of these variables to `1` causes the program to print a message to the console
at startup and wait for a debugger to attach. Set to the string `vs` to use the VS JIT
Debug dialog instead (requires Windows and a Visual Studio installation).

Also `TRACE_NODE_API_HOST` causes tracing information to be printed about the the process of
loading the .NET host.
## Tracing
The following environment variables trigger verbose tracing to the console:
- `NODE_API_TRACE_HOST` - Trace messages about starting the native host and managed host and
dynanically exporting .NET types from the managed host to JS.
- `NODE_API_TRACE_RUNTIME` - Trace all calls and callbacks across the JS/.NET boundary.
Tracing works with both debug and release builds.

## Check/fix formatting
PR builds will fail if formatting does not comply with settings in `.editorconfig`.
Expand Down
32 changes: 18 additions & 14 deletions src/NodeApi.DotNetHost/JSMarshaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1137,7 +1137,7 @@ public Expression<Func<JSCallbackDescriptor>> BuildConstructorOverloadDescriptor
* new Type[] { ... }, // Constructor overload parameter types
* (args) => { ... }); // Constructor overload lambda
* ... // Additional overloads
* return JSCallbackOverload.CreateDescriptor(overloads);
* return JSCallbackOverload.CreateDescriptor(typeName, overloads);
*/

ParameterExpression overloadsVariable =
Expand Down Expand Up @@ -1166,9 +1166,12 @@ public Expression<Func<JSCallbackDescriptor>> BuildConstructorOverloadDescriptor
}

MethodInfo createDescriptorMethod = typeof(JSCallbackOverload).GetStaticMethod(
nameof(JSCallbackOverload.CreateDescriptor));
nameof(JSCallbackOverload.CreateDescriptor),
new Type[] { typeof(string), typeof(JSCallbackOverload[]) });
statements[statements.Length - 1] = Expression.Call(
createDescriptorMethod, overloadsVariable);
createDescriptorMethod,
Expression.Constant(constructors[0].DeclaringType!.Name),
overloadsVariable);

return (Expression<Func<JSCallbackDescriptor>>)Expression.Lambda(
typeof(Func<JSCallbackDescriptor>),
Expand All @@ -1181,10 +1184,9 @@ public Expression<Func<JSCallbackDescriptor>> BuildConstructorOverloadDescriptor
}

/// <summary>
/// Builds a callback descriptor that resolves and invokes the best-matching overload from
/// a set of overloaded constructors.
/// Gets overload information for a set of constructors.
/// </summary>
public JSCallbackDescriptor BuildConstructorOverloadDescriptor(ConstructorInfo[] constructors)
public JSCallbackOverload[] GetConstructorOverloads(ConstructorInfo[] constructors)
{
JSCallbackOverload[] overloads = new JSCallbackOverload[constructors.Length];
for (int i = 0; i < constructors.Length; i++)
Expand All @@ -1203,7 +1205,7 @@ public JSCallbackDescriptor BuildConstructorOverloadDescriptor(ConstructorInfo[]
BuildFromJSConstructorExpression(constructors[i]).Compile();
overloads[i] = new JSCallbackOverload(parameterTypes, constructorDelegate);
}
return JSCallbackOverload.CreateDescriptor(overloads);
return overloads;
}

/// <summary>
Expand All @@ -1225,7 +1227,7 @@ public Expression<Func<JSCallbackDescriptor>> BuildMethodOverloadDescriptorExpre
* new Type[] { ... }, // Method overload parameter types
* (args) => { ... }); // Method overload lambda
* ... // Additional overloads
* return JSCallbackOverload.CreateDescriptor(overloads);
* return JSCallbackOverload.CreateDescriptor(methodName, overloads);
*/

ParameterExpression overloadsVariable =
Expand Down Expand Up @@ -1254,9 +1256,12 @@ public Expression<Func<JSCallbackDescriptor>> BuildMethodOverloadDescriptorExpre
}

MethodInfo createDescriptorMethod = typeof(JSCallbackOverload).GetStaticMethod(
nameof(JSCallbackOverload.CreateDescriptor));
nameof(JSCallbackOverload.CreateDescriptor),
new Type[] { typeof(string), typeof(JSCallbackOverload[]) });
statements[statements.Length - 1] = Expression.Call(
createDescriptorMethod, overloadsVariable);
createDescriptorMethod,
Expression.Constant(methods[0].Name),
overloadsVariable);

return (Expression<Func<JSCallbackDescriptor>>)Expression.Lambda(
typeof(Func<JSCallbackDescriptor>),
Expand All @@ -1269,10 +1274,9 @@ public Expression<Func<JSCallbackDescriptor>> BuildMethodOverloadDescriptorExpre
}

/// <summary>
/// Builds a callback descriptor that resolves and invokes the best-matching overload from
/// a set of overloaded methods.
/// Gets overload information for a set of overloaded methods.
/// </summary>
public JSCallbackDescriptor BuildMethodOverloadDescriptor(MethodInfo[] methods)
public JSCallbackOverload[] GetMethodOverloads(MethodInfo[] methods)
{
JSCallbackOverload[] overloads = new JSCallbackOverload[methods.Length];
for (int i = 0; i < methods.Length; i++)
Expand All @@ -1292,7 +1296,7 @@ public JSCallbackDescriptor BuildMethodOverloadDescriptor(MethodInfo[] methods)
BuildFromJSMethodExpression(methods[i]).Compile();
overloads[i] = new JSCallbackOverload(parameterTypes, defaultValues, methodDelegate);
}
return JSCallbackOverload.CreateDescriptor(overloads);
return overloads;
}

private Expression<JSCallback> BuildFromJSStaticMethodExpression(MethodInfo method)
Expand Down
14 changes: 9 additions & 5 deletions src/NodeApi.DotNetHost/ManagedHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ JSValue removeListener(JSCallbackArgs args)

public static bool IsTracingEnabled { get; } =
Debugger.IsAttached ||
Environment.GetEnvironmentVariable("TRACE_NODE_API_HOST") == "1";
Environment.GetEnvironmentVariable("NODE_API_TRACE_HOST") == "1";

public static void Trace(string msg)
{
Expand Down Expand Up @@ -211,12 +211,12 @@ public static napi_value InitializeModule(napi_env env, napi_value exports)
Trace($" .NET Runtime version: {Environment.Version}");
#endif

DebugHelper.AttachDebugger("DEBUG_NODE_API_RUNTIME");
DebugHelper.AttachDebugger("NODE_API_DEBUG_RUNTIME");

JSRuntime runtime = new NodejsRuntime();

if (Debugger.IsAttached ||
Environment.GetEnvironmentVariable("TRACE_NODE_API_RUNTIME") != null)
Environment.GetEnvironmentVariable("NODE_API_TRACE_RUNTIME") != null)
{
TraceSource trace = new(typeof(JSValue).Namespace!);
trace.Switch.Level = SourceLevels.All;
Expand Down Expand Up @@ -554,9 +554,13 @@ private void LoadAssemblyTypes(Assembly assembly)
continue;
}

// Delay-loading is enabled by default, but can be disabled with this env variable.
bool deferMembers = Environment.GetEnvironmentVariable("NODE_API_DELAYLOAD") != "0";

if (!_exportedNamespaces.TryGetValue(namespaceParts[0], out Namespace? parentNamespace))
{
parentNamespace = new Namespace(namespaceParts[0], _typeExporter.TryExportType);
parentNamespace = new Namespace(
namespaceParts[0], (type) => _typeExporter.TryExportType(type, deferMembers));
_exports.GetValue()!.Value.SetProperty(namespaceParts[0], parentNamespace.Value);
_exportedNamespaces.Add(namespaceParts[0], parentNamespace);
}
Expand All @@ -568,7 +572,7 @@ private void LoadAssemblyTypes(Assembly assembly)
{
childNamespace = new Namespace(
parentNamespace.Name + '.' + namespaceParts[i],
_typeExporter.TryExportType);
(type) => _typeExporter.TryExportType(type, deferMembers));
parentNamespace.Namespaces.Add(namespaceParts[i], childNamespace);
}

Expand Down
Loading

0 comments on commit a9b983a

Please sign in to comment.