Skip to content

Commit 72c3566

Browse files
committed
.NET: Add caching for proxy objects
1 parent e8421cd commit 72c3566

File tree

7 files changed

+191
-13
lines changed

7 files changed

+191
-13
lines changed

csharp-api/REFrameworkNET/IObject.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace REFrameworkNET {
88
ref class TypeDefinition;
9+
interface class IProxy;
910
value struct InvokeRet;
1011

1112
// Base interface of ManagedObject and NativeObject
@@ -19,5 +20,7 @@ public interface class IObject : public IProxyable, public System::IEquatable<IO
1920
// For interface types
2021
generic <typename T>
2122
T As();
23+
24+
IProxy^ GetProxy(System::Type^ proxyType);
2225
};
2326
}

csharp-api/REFrameworkNET/ManagedObject.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ namespace REFrameworkNET {
1919
void ManagedObject::CleanupKnownCaches() {
2020
Cache<ManagedObject>::CleanupAll();
2121
Cache<SystemString>::CleanupAll();
22+
UnifiedObject::ProxyPool::Clear();
2223
}
2324

2425
void ManagedObject::Internal_Finalize() {
25-
if (m_object == nullptr) {
26+
if (m_object == nullptr || !m_initialized) {
2627
return;
2728
}
2829

30+
UnifiedObject::ProxyPool::Remove(this);
2931
ReleaseIfGlobalized();
3032

3133
// Only if we are not marked as weak are we allowed to release the object, even if the object is globalized
@@ -106,6 +108,18 @@ namespace REFrameworkNET {
106108

107109
generic <typename T>
108110
T ManagedObject::As() {
109-
return ManagedProxy<T>::Create(this);
111+
// Don't bother caching if we are not globalized (ephemeral/local objects)
112+
if (!IsGlobalized()) {
113+
return ManagedProxy<T>::Create(this);
114+
}
115+
116+
if (auto existingProxy = this->GetProxy(T::typeid)) {
117+
return (T)existingProxy;
118+
}
119+
120+
auto result = ManagedProxy<T>::Create(this);
121+
122+
this->AddProxy(T::typeid, (IProxy^)result);
123+
return result;
110124
}
111125
}

csharp-api/REFrameworkNET/ManagedObject.hpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -336,23 +336,28 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject
336336
generic <typename T>
337337
virtual T As() override;
338338

339-
// TODO methods:
340-
/*public Void* GetReflectionProperties() {
341-
return _original.get_reflection_properties();
342-
}*/
339+
virtual IProxy^ GetProxy(System::Type^ type) override {
340+
if (!IsGlobalized()) {
341+
return nullptr;
342+
}
343343

344-
/*public ReflectionProperty GetReflectionPropertyDescriptor(basic_string_view<char\, std::char_traits<char>> name) {
345-
return _original.get_reflection_property_descriptor(name);
344+
return UnifiedObject::GetProxy(type);
346345
}
347346

348-
public ReflectionMethod GetReflectionMethodDescriptor(basic_string_view<char\, std::char_traits<char>> name) {
349-
return _original.get_reflection_method_descriptor(name);
350-
}*/
347+
internal:
348+
virtual void AddProxy(System::Type^ type, IProxy^ proxy) override {
349+
if (!IsGlobalized()) {
350+
return;
351+
}
352+
353+
UnifiedObject::AddProxy(type, proxy);
354+
}
351355

352356
internal:
353357
static bool ShuttingDown = false;
354358

355359
void Deinitialize() {
360+
m_object = nullptr;
356361
m_initialized = false;
357362
}
358363

csharp-api/REFrameworkNET/NativeObject.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@
88
namespace REFrameworkNET {
99
generic <typename T>
1010
T NativeObject::As() {
11+
// Cannot cache nullptr objects
12+
/*if (this->GetAddress() == 0) {
13+
return NativeProxy<T>::Create(this);
14+
}
15+
16+
if (auto existingProxy = this->GetProxy(T::typeid)) {
17+
return (T)existingProxy;
18+
}
19+
20+
auto result = NativeProxy<T>::Create(this);
21+
22+
this->AddProxy(T::typeid, (IProxy^)result);
23+
return result;*/
24+
25+
// Not gonna bother with this for now. Just create a new proxy every time
26+
// Reason being, this is not a managed object, so we have no idea if the address
27+
// still points to the original object or not. It's better to just create a new one.
1128
return NativeProxy<T>::Create(this);
1229
}
1330
}

csharp-api/REFrameworkNET/PluginManager.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ namespace REFrameworkNET {
692692
NativePool<TypeDefinition>::DisplayStats();
693693
NativePool<Method>::DisplayStats();
694694
NativePool<Field>::DisplayStats();
695+
UnifiedObject::ProxyPool::DisplayStats();
695696

696697
ImGuiNET::ImGui::TreePop();
697698
}

csharp-api/REFrameworkNET/Proxy.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public
7474
return Instance->As<T>();
7575
}
7676

77+
virtual IProxy^ GetProxy(System::Type^ type) {
78+
return Instance->GetProxy(type);
79+
}
80+
7781
static T Create(IObject^ target) {
7882
auto proxy = Reflection::DispatchProxy::Create<T, Proxy<T, T2>^>();
7983
((IProxy^)proxy)->SetInstance(target);
@@ -130,6 +134,15 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public
130134
return !left->Equals(right);
131135
}
132136

137+
internal:
138+
~Proxy() {
139+
this->!Proxy();
140+
}
141+
142+
!Proxy() {
143+
144+
}
145+
133146
protected:
134147
virtual Object^ Invoke(Reflection::MethodInfo^ targetMethod, array<Object^>^ args) override {
135148
// Get the REFrameworkNET::Attributes::Method attribute from the method
@@ -203,8 +216,18 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public
203216
auto iobjectResult = dynamic_cast<REFrameworkNET::IObject^>(result);
204217

205218
if (iobjectResult != nullptr && targetReturnType->IsInterface) {
219+
// Caching mechanism to prevent creating multiple proxies for the same object and type so we dont stress the GC
220+
if (auto existingProxy = iobjectResult->GetProxy(targetReturnType); existingProxy != nullptr) {
221+
return existingProxy;
222+
}
223+
206224
auto proxy = DispatchProxy::Create(targetReturnType, Proxy<T, T2>::typeid->GetGenericTypeDefinition()->MakeGenericType(T::typeid, result->GetType()));
207225
((IProxy^)proxy)->SetInstance(iobjectResult);
226+
227+
if (auto unified = dynamic_cast<REFrameworkNET::UnifiedObject^>(iobjectResult); unified != nullptr) {
228+
unified->AddProxy(targetReturnType, (IProxy^)proxy);
229+
}
230+
208231
result = proxy;
209232
return result;
210233
}

csharp-api/REFrameworkNET/UnifiedObject.hpp

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
namespace REFrameworkNET {
77
ref class TypeDefinition;
8+
interface class IProxy;
89
value struct InvokeRet;
910

1011
// UnifiedObject is the base class that ManagedObject and NativeObject will derive from
@@ -22,8 +23,11 @@ public ref class UnifiedObject abstract : public System::Dynamic::DynamicObject,
2223

2324
generic <typename T>
2425
virtual T As() abstract = 0;
25-
26-
// Shared methods
26+
27+
virtual IProxy^ GetProxy(System::Type^ type) {
28+
return ProxyPool::GetIfExists(this, type);
29+
}
30+
2731
virtual bool HandleInvokeMember_Internal(System::String^ methodName, array<System::Object^>^ args, System::Object^% result);
2832
virtual bool HandleInvokeMember_Internal(uint32_t methodIndex, array<System::Object^>^ args, System::Object^% result);
2933
virtual bool HandleInvokeMember_Internal(System::Object^ methodObj, array<System::Object^>^ args, System::Object^% result);
@@ -104,5 +108,116 @@ public ref class UnifiedObject abstract : public System::Dynamic::DynamicObject,
104108
virtual System::Collections::IEnumerator^ GetEnumerator() {
105109
return gcnew REFrameworkNET::ObjectEnumerator(this);
106110
}
111+
112+
internal:
113+
using WeakProxy = System::WeakReference<IProxy^>;
114+
115+
generic <typename T>
116+
T AsCached() {
117+
auto strongProxy = GetProxy(T::typeid);
118+
119+
if (strongProxy != nullptr) {
120+
return (T)strongProxy;
121+
}
122+
123+
T instance = As<T>();
124+
//m_proxies->TryAdd(T::typeid, gcnew WeakProxy((IProxy^)instance));
125+
ProxyPool::Add(this, T::typeid, (IProxy^)instance);
126+
127+
return instance;
128+
}
129+
130+
virtual void AddProxy(System::Type^ type, IProxy^ proxy) {
131+
//m_proxies->TryAdd(type, gcnew WeakProxy(proxy));
132+
ProxyPool::Add(this, type, proxy);
133+
}
134+
135+
protected:
136+
// It's weak because internally the proxy will hold a strong reference to the object
137+
/*System::Collections::Concurrent::ConcurrentDictionary<System::Type^, WeakProxy^>^ m_proxies
138+
= gcnew System::Collections::Concurrent::ConcurrentDictionary<System::Type^, WeakProxy^>(System::Environment::ProcessorCount * 2, 8);*/
139+
140+
internal:
141+
// Using a proxy cache that is separate from the object itself
142+
// because holding weak references in the object itself is asking for trouble
143+
ref class ProxyPool {
144+
internal:
145+
using WeakT = System::WeakReference<IProxy^>;
146+
147+
static WeakT^ AddValueFactory(System::Type^ key, IProxy^ arg) {
148+
return gcnew WeakT(arg);
149+
}
150+
151+
static System::Func<System::Type^, IProxy^, WeakT^>^ addValueFactory = gcnew System::Func<System::Type^, IProxy^, WeakT^>(AddValueFactory);
152+
153+
static WeakT^ UpdateFactory(System::Type^ key, WeakT^ value, IProxy^ arg) {
154+
return gcnew WeakT(arg);
155+
}
156+
157+
static System::Func<System::Type^, WeakT^, IProxy^, WeakT^>^ updateFactory = gcnew System::Func<System::Type^, WeakT^, IProxy^, WeakT^>(UpdateFactory);
158+
159+
static IProxy^ GetIfExists(IObject^ obj, System::Type^ type) {
160+
const auto addr = obj->GetAddress();
161+
162+
if (addr == 0) {
163+
return nullptr;
164+
}
165+
166+
InternalDict^ dict = nullptr;
167+
if (!s_cache->TryGetValue(addr, dict)) {
168+
return nullptr;
169+
}
170+
171+
WeakT^ weak = nullptr;
172+
if (!dict->TryGetValue(type, weak)) {
173+
return nullptr;
174+
}
175+
176+
IProxy^ target = nullptr;
177+
if (!weak->TryGetTarget(target)) {
178+
return nullptr;
179+
}
180+
181+
return target;
182+
}
183+
184+
static void Add(IObject^ obj, System::Type^ type, IProxy^ proxy) {
185+
InternalDict^ dict = nullptr;
186+
const auto hashcode = obj->GetAddress();
187+
188+
if (hashcode == 0) {
189+
return;
190+
}
191+
192+
if (!s_cache->TryGetValue(hashcode, dict)) {
193+
dict = gcnew InternalDict(System::Environment::ProcessorCount * 2, 2);
194+
s_cache->TryAdd(hashcode, dict);
195+
}
196+
197+
dict->AddOrUpdate(type, addValueFactory, updateFactory, proxy);
198+
}
199+
200+
static void Remove(IObject^ obj) {
201+
InternalDict^ dict = nullptr;
202+
s_cache->TryRemove(obj->GetAddress(), dict);
203+
}
204+
205+
static void Clear() {
206+
s_cache->Clear();
207+
}
208+
209+
static void DisplayStats() {
210+
if (ImGuiNET::ImGui::TreeNode("ProxyPool")) {
211+
ImGuiNET::ImGui::Text("Cache size: " + s_cache->Count.ToString());
212+
ImGuiNET::ImGui::TreePop();
213+
}
214+
}
215+
216+
private:
217+
// Not using the actual object as the key so that we can avoid holding strong references to the objects
218+
using InternalDict = System::Collections::Concurrent::ConcurrentDictionary<System::Type^, WeakT^>;
219+
using DictT = System::Collections::Concurrent::ConcurrentDictionary<uintptr_t, InternalDict^>;
220+
static DictT^ s_cache = gcnew DictT(System::Environment::ProcessorCount * 2, 8192);
221+
};
107222
};
108223
}

0 commit comments

Comments
 (0)