Skip to content

Commit

Permalink
[MTGOSDK/API] ObjectProvider: Enable lazy instantiation
Browse files Browse the repository at this point in the history
Defaults to lazy instantiation of dynamic remote objects, deferring instantiation to a later call to ObjectProvider.

This implements the same functionality between RemoteProxy and LazyRemoteObject, allowing for multiple lazy initialization schemes to be daisy chained.

This cover cases where an object's members would need to be enumerated to bind an interface type, and also where only the object reference is needed.
  • Loading branch information
Qonfused committed Jun 1, 2024
1 parent 717316b commit 3fbe6f7
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 43 deletions.
42 changes: 35 additions & 7 deletions MTGOSDK/src/API/ObjectProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public static class ObjectProvider
/// </summary>
private static readonly ConcurrentDictionary<string, dynamic> s_instances = new();

/// <summary>
/// Store of all instances' setters for retrieved LazyRemoteObjects.
/// </summary>
private static readonly ConcurrentDictionary<string, Func<Func<dynamic>, dynamic>> s_resetters = new();

/// <summary>
/// Whether the ObjectProvider cache requires a refresh.
/// </summary>
Expand All @@ -41,27 +46,50 @@ public static class ObjectProvider
/// <param name="queryPath">The query path of the registered type.</param>
/// <param name="useCache">Whether to use the cached instance.</param>
/// <param name="useHeap">Whether to query the client's object heap.</param>
/// <param name="useLazy">Whether to lazy-load the returned instance.</param>
/// <returns>A remote instance of the given type.</returns>
public static dynamic Get(
string queryPath,
bool useCache = true,
bool useHeap = false)
bool useHeap = false,
bool useLazy = true)
{
// Check if the instance is already cached
if (useCache && s_instances.TryGetValue(queryPath, out dynamic instance))
{
Log.Trace("Retrieved cached instance type {Type}", queryPath);

if (RequiresRefresh)
Log.Warning("Retrieved type is stale and requires a refresh from ObjectProvider.");

return instance;
}

//
// Create a LazyRemoteObject instance and store its resetter for future use.
//
// If the instance is never referenced, then the ObjectProvider will never
// query the client for the instance type and create no overhead.
//
if (useLazy)
{
Log.Trace("Creating lazy instance of type {Type}", queryPath);
instance = new LazyRemoteObject();
var resetter = instance.Set(new Func<dynamic>(() =>
Get(queryPath, useCache, useHeap, useLazy: false)));
s_resetters.TryAdd(queryPath, resetter);

return instance;
}
// If the client is disposed, return an empty instance to defer construction
if (RemoteClient.IsDisposed)
else if (RemoteClient.IsDisposed)
{
Log.Trace("Client is disposed. Returning empty instance of type {Type}", queryPath);
instance = new DynamicRemoteObject();
return Get(queryPath, useCache, useHeap, useLazy: true);
}

// Query using the ObjectProvider.Get<T>() method on the client
else if (!useHeap)
if (!useHeap)
{
// Get the RemoteType from the type's query path
Log.Trace("Retrieving instance type {Type}", queryPath);
Expand Down Expand Up @@ -140,9 +168,9 @@ public static void Refresh()
Log.Debug("Refreshing ObjectProvider cache.");
foreach (var kvp in s_instances)
{
s_instances.TryGetValue(kvp.Key, out dynamic refObj);
dynamic obj = Get(kvp.Key, useCache: false);
Swap(ref refObj, obj, bindTypes: true);
Log.Trace("Refreshing instance type {Type}", kvp.Key);
s_resetters.TryGetValue(kvp.Key, out var s_reset);
s_reset(new Func<dynamic>(() => Get(kvp.Key, useCache: false, useLazy: false)));
}
RequiresRefresh = false;
}
Expand Down
25 changes: 1 addition & 24 deletions MTGOSDK/src/Core/Reflection/DLRWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ internal virtual dynamic @base
/// </summary>
internal dynamic @ro => Try(() => Unbind(@base).__ro, () => @base.__ro)
?? throw new InvalidOperationException(
$"{type.Name} type does not implement RemoteObject.");
$"{type.Name} type does not implement DynamicRemoteObject.");

//
// Deferred remote object initialization.
Expand Down Expand Up @@ -166,29 +166,6 @@ public static T Defer<T>(Func<T> c) where T : class
return proxy;
}

/// <summary>
/// Swaps the dynamic remote object handle with another instance.
/// </summary>
/// <param name="refObj">The reference object to swap.</param>
/// <param name="obj">The object to swap with.</param>
/// <param name="bindTypes">Whether to bind reference types (optional).</param>
/// <returns>The reference object with the swapped remote object handle.</returns>
public static dynamic Swap(
ref dynamic refObj,
dynamic obj,
bool bindTypes = false)
{
// Extract the remote object handle from the source object.
var objDro = bindTypes ? obj : Unbind(obj);

// Copy the remote object handle to the reference object.
refObj.__ra = objDro.__ra;
refObj.__ro = objDro.__ro;
refObj.__type = objDro.__type;

return refObj;
}

//
// Wrapper methods for type casting and dynamic dispatching.
//
Expand Down
22 changes: 11 additions & 11 deletions MTGOSDK/src/Core/Remoting/Internal/DynamicRemoteObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,19 @@ public override int GetHashCode()
this[t1, t2, t3, t4][t5];
}

public RemoteHandle __ra;
public RemoteObject __ro;
public RemoteType __type;
public virtual RemoteHandle __ra { get => m_ra; set => m_ra = value; }
private RemoteHandle m_ra = null!;

public virtual RemoteObject __ro { get => m_ro; set => m_ro = value; }
private RemoteObject m_ro = null!;

public virtual RemoteType __type { get => m_type; set => m_type = value; }
private RemoteType m_type = null!;

private IEnumerable<MemberInfo> __ongoingMembersDumper = null;
private IEnumerator<MemberInfo> __ongoingMembersDumperEnumerator = null;
private List<MemberInfo> __membersInner = null;
public IEnumerable<MemberInfo> __members => MindFuck();
public IEnumerable<MemberInfo> __members => GetMembers();

public DynamicRemoteObject(RemoteHandle ra, RemoteObject ro)
{
Expand All @@ -184,12 +189,7 @@ public DynamicRemoteObject(RemoteHandle ra, RemoteObject ro)
}
}

public DynamicRemoteObject() // For avoiding overriding reference type
{
__ra = null;
__ro = null;
__type = null;
}
public DynamicRemoteObject() { } // For avoiding overriding reference type

/// <summary>
/// Gets the type of the proxied remote object, in the remote app. (This does not reutrn `typeof(DynamicRemoteMethod)`)
Expand Down Expand Up @@ -229,7 +229,7 @@ private IEnumerable<MemberInfo> GetAllMembersRecursive()
while (nextType != null && lastType != typeof(object));
}

private IEnumerable<MemberInfo> MindFuck()
private IEnumerable<MemberInfo> GetMembers()
{
if (__membersInner != null && __ongoingMembersDumper == null)
{
Expand Down
46 changes: 46 additions & 0 deletions MTGOSDK/src/Core/Remoting/Internal/LazyRemoteObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/** @file
Copyright (c) 2024, Cory Bennett. All rights reserved.
SPDX-License-Identifier: Apache-2.0
**/

using MTGOSDK.Core.Remoting.Internal.Reflection;


namespace MTGOSDK.Core.Remoting.Internal;

/// <summary>
/// A wrapper for lazy initializing dynamic remote objects.
/// </summary>
public class LazyRemoteObject : DynamicRemoteObject
{
public override RemoteHandle __ra => __ctor?.Value?.__ra!;
public override RemoteObject __ro => __ctor?.Value?.__ro!;
public override RemoteType __type => __ctor?.Value?.__type!;

/// <summary>
/// The lazy object to defer initialization of the remote object.
/// </summary>
private Lazy<dynamic> __ctor = null!;

/// <summary>
/// Sets the lazy initializer function for the remote object.
/// </summary>
/// <param name="c">The lazy initializer function.</param>
/// <returns>A reference to this function.</returns>
public Func<Func<dynamic>, dynamic> Set(Func<dynamic>? c1)
{
__ctor = c1 != null ? new(() => c1!.Invoke()) : null!;
return new Func<Func<dynamic>, dynamic>(c => Set(c));
}

/// <summary>
/// Sets the lazy object for the remote object.
/// </summary>
/// <param name="c">The lazy object.</param>
/// <returns>A reference to this function.</returns>
public Func<Lazy<dynamic>, dynamic> Set(Lazy<dynamic>? c2)
{
__ctor = c2!;
return new Func<Lazy<dynamic>, dynamic>(c => Set(c));
}
}
10 changes: 9 additions & 1 deletion MTGOSDK/src/Core/Remoting/RemoteProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,15 @@ internal void Construct()
lock (refLock)
{
if (refObj.__ra is null)
Swap(ref refObj, obj.Value);
{
// Extract the remote object handle from the lazy object.
var dro = Unbind(obj.Value);

// Copy the remote object handle to the reference object.
refObj.__ra ??= dro.__ra;
refObj.__ro ??= dro.__ro;
refObj.__type ??= dro.__type;
}
}
}

Expand Down

0 comments on commit 3fbe6f7

Please sign in to comment.