Skip to content

Commit

Permalink
.NET: Initial support for static fields w/ reference assemblies
Browse files Browse the repository at this point in the history
  • Loading branch information
praydog committed May 13, 2024
1 parent ea85cdd commit 695c1de
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 88 deletions.
43 changes: 32 additions & 11 deletions csharp-api/AssemblyGenerator/ClassGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,24 +352,45 @@ private TypeDeclarationSyntax GenerateFields(List<SimpleBaseTypeSyntax> baseType
SyntaxFactory.ParseAttributeArgumentList("(" + field.Index.ToString() + ", global::REFrameworkNET.FieldFacadeType.Setter)"))
);
var properyDeclaration = SyntaxFactory.PropertyDeclaration(fieldType, fieldName)
.AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)})
.AddAccessorListAccessors(
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).AddAttributeLists(fieldFacadeGetter)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).AddAttributeLists(fieldFacadeSetter)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
);
AccessorDeclarationSyntax getter = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).AddAttributeLists(fieldFacadeGetter);
AccessorDeclarationSyntax setter = SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).AddAttributeLists(fieldFacadeSetter);
var propertyDeclaration = SyntaxFactory.PropertyDeclaration(fieldType, fieldName)
.AddModifiers([SyntaxFactory.Token(SyntaxKind.PublicKeyword)]);
if (field.IsStatic()) {
properyDeclaration = properyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
// Now we must add a body to it that actually calls the method
// We have our REFType field, so we can lookup the method and call it
// Make a private static field to hold the REFrameworkNET.Method
var internalFieldName = "INTERNAL_" + fieldName + field.GetIndex().ToString();
var fieldVariableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("global::REFrameworkNET.Field"))
.AddVariables(SyntaxFactory.VariableDeclarator(internalFieldName).WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("REFType.GetField(\"" + field.GetName() + "\")"))));
var fieldDeclaration = SyntaxFactory.FieldDeclaration(fieldVariableDeclaration).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword));
internalFieldDeclarations.Add(fieldDeclaration);
List<StatementSyntax> bodyStatementsSetter = [];
List<StatementSyntax> bodyStatementsGetter = [];
bodyStatementsGetter.Add(SyntaxFactory.ParseStatement("return (" + fieldType.GetText().ToString() + ")" + internalFieldName + ".GetDataBoxed(typeof(" + fieldType.GetText().ToString() + "), 0, false);"));
bodyStatementsSetter.Add(SyntaxFactory.ParseStatement(internalFieldName + ".SetDataBoxed(0, new object[] {value}, false);"));
getter = getter.AddBodyStatements(bodyStatementsGetter.ToArray());
setter = setter.AddBodyStatements(bodyStatementsSetter.ToArray());
} else {
getter = getter.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
setter = setter.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
}
propertyDeclaration = propertyDeclaration.AddAccessorListAccessors(getter, setter);
if (this.t.ParentType != null && this.t.ParentType.FindField(field.Name) != null) {
properyDeclaration = properyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword));
propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword));
}
return properyDeclaration;
return propertyDeclaration;
});

return typeDeclaration.AddMembers(matchingFields.ToArray());
Expand Down
2 changes: 2 additions & 0 deletions csharp-api/AssemblyGenerator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,8 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin
REFrameworkNET.API.LocalFrameGC();
}

System.Console.WriteLine("Compiling " + strippedAssemblyName + " with " + compilationUnits.Count + " compilation units...");

List<SyntaxTree> syntaxTrees = new List<SyntaxTree>();

var syntaxTreeParseOption = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12);
Expand Down
30 changes: 29 additions & 1 deletion csharp-api/REFrameworkNET/Field.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "ManagedObject.hpp"
#include "Utility.hpp"
#include "ValueType.hpp"
#include "Proxy.hpp"

#include "Field.hpp"

Expand Down Expand Up @@ -35,6 +37,10 @@ namespace REFrameworkNET {
return Utility::BoxData((uintptr_t*)raw_data, this->Type, false);
}

System::Object^ Field::GetDataBoxed(System::Type^ targetReturnType, uintptr_t obj, bool isValueType) {
return Utility::TranslateBoxedData(targetReturnType, GetDataBoxed(obj, isValueType));
}

void Field::SetDataBoxed(uintptr_t obj, System::Object^ value, bool isValueType) {
auto data_ptr = GetDataRaw(obj, isValueType);

Expand All @@ -49,10 +55,19 @@ namespace REFrameworkNET {
}

if (!field_type->IsValueType()) {
const auto iobject = dynamic_cast<REFrameworkNET::IObject^>(value);
auto iobject = dynamic_cast<REFrameworkNET::IObject^>(value);

if (iobject != nullptr) {
*(uintptr_t*)data_ptr = iobject->GetAddress();

// Add a reference onto the object now that something else is holding onto it
auto proxy = dynamic_cast<REFrameworkNET::IProxy^>(iobject);
auto managedObject = proxy != nullptr ? dynamic_cast<REFrameworkNET::ManagedObject^>(proxy->GetInstance()) : dynamic_cast<REFrameworkNET::ManagedObject^>(iobject);

if (managedObject != nullptr) {
managedObject->Globalize(); // Globalize it if it's not already
managedObject->AddRef(); // Add a "dangling" reference
}
}

return; // Don't think there's any other way to set a reference type
Expand Down Expand Up @@ -83,7 +98,20 @@ namespace REFrameworkNET {
MAKE_TYPE_HANDLER_SET("System.UIntPtr", uintptr_t)

default:
{
const auto iobject = dynamic_cast<REFrameworkNET::IObject^>(value);
const auto iobject_td = iobject != nullptr ? iobject->GetTypeDefinition() : nullptr;

if (iobject != nullptr && iobject_td == field_type) {
if (iobject->IsManaged()) {
memcpy((void*)data_ptr, (void*)((uintptr_t)iobject->Ptr() + iobject_td->GetFieldPtrOffset()), field_type->ValueTypeSize);
} else {
memcpy((void*)data_ptr, iobject->Ptr(), field_type->ValueTypeSize);
}
}

break;
}
}
}
}
9 changes: 9 additions & 0 deletions csharp-api/REFrameworkNET/Field.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ public ref class Field {
/// <returns>The field data as a boxed object</returns>
System::Object^ GetDataBoxed(uintptr_t obj, bool isValueType);

/// <summary>
/// Get the field data as a boxed object, but proxies the object to the target type
/// </summary>
/// <param name="targetReturnType">The target type to proxy the object to.</param>
/// <param name="obj">The object to get the field data from</param>
/// <param name="isValueType">Whether the object holding the field is a value type</param>
/// <returns>The field data as a boxed object</returns>
System::Object^ GetDataBoxed(System::Type^ targetReturnType, uintptr_t obj, bool isValueType);

/// <summary>
/// Set the field data from a boxed object
/// </summary>
Expand Down
74 changes: 1 addition & 73 deletions csharp-api/REFrameworkNET/Method.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,83 +133,11 @@ REFrameworkNET::InvokeRet Method::Invoke(System::Object^ obj, array<System::Obje
return REFrameworkNET::InvokeRet::FromNative(Invoke_Internal(obj, args));
}

public interface class DummyInterface {

};

System::Object^ Method::InvokeBoxed(System::Type^ targetReturnType, System::Object^ obj, array<System::Object^>^ args) {
System::Object^ result = nullptr;
this->HandleInvokeMember_Internal(obj, args, result);

if (result == nullptr) {
return nullptr; // ez
}

if (targetReturnType == nullptr) {
return result;
}

if (!targetReturnType->IsPrimitive && !targetReturnType->IsEnum && !targetReturnType->IsInterface) {
if (targetReturnType == String::typeid) {
return result;
}

if (targetReturnType == REFrameworkNET::ManagedObject::typeid) {
return result;
}

if (targetReturnType == REFrameworkNET::NativeObject::typeid) {
return result;
}

if (targetReturnType == REFrameworkNET::TypeDefinition::typeid) {
return result;
}

if (targetReturnType == REFrameworkNET::Method::typeid) {
return result;
}

if (targetReturnType == REFrameworkNET::Field::typeid) {
return result;
}
}

if (targetReturnType->IsEnum) {
auto underlyingType = targetReturnType->GetEnumUnderlyingType();

if (underlyingType != nullptr) {
auto underlyingResult = Convert::ChangeType(result, underlyingType);
return Enum::ToObject(targetReturnType, underlyingResult);
}
}

if (this->DeclaringType == nullptr) {
return result;
}

if (!targetReturnType->IsPrimitive && targetReturnType->IsInterface) {
auto iobjectResult = dynamic_cast<REFrameworkNET::IObject^>(result);

if (iobjectResult != nullptr && targetReturnType->IsInterface) {
// Caching mechanism to prevent creating multiple proxies for the same object and type so we dont stress the GC
if (auto existingProxy = iobjectResult->GetProxy(targetReturnType); existingProxy != nullptr) {
return existingProxy;
}

auto proxy = System::Reflection::DispatchProxy::Create(targetReturnType, Proxy<DummyInterface^, IObject^>::typeid->GetGenericTypeDefinition()->MakeGenericType(targetReturnType, result->GetType()));
((IProxy^)proxy)->SetInstance(iobjectResult);

if (auto unified = dynamic_cast<REFrameworkNET::UnifiedObject^>(iobjectResult); unified != nullptr) {
unified->AddProxy(targetReturnType, (IProxy^)proxy);
}

result = proxy;
return result;
}
}

return result;
return Utility::TranslateBoxedData(targetReturnType, result);
}

::reframework::InvokeRet Method::Invoke_Internal(System::Object^ obj, array<System::Object^>^ args) {
Expand Down
74 changes: 74 additions & 0 deletions csharp-api/REFrameworkNET/Utility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
#include "TypeDefinition.hpp"
#include "Field.hpp"
#include "Method.hpp"
#include "Proxy.hpp"

#include "Utility.hpp"

namespace REFrameworkNET {
public interface class DummyInterface {

};

System::Object^ Utility::BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInvoke) {
System::Object^ result = nullptr;

Expand Down Expand Up @@ -122,10 +127,79 @@ System::Object^ Utility::BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInv
// Here's hoping this works
auto vt = gcnew REFrameworkNET::ValueType(t);
memcpy(vt->Ptr(), ptr, t->ValueTypeSize);
result = vt;
}
break;
}

return result;
}

System::Object^ Utility::TranslateBoxedData(System::Type^ targetReturnType, System::Object^ result) {
if (result == nullptr) {
return nullptr; // ez
}

if (targetReturnType == nullptr) {
return result;
}

if (!targetReturnType->IsPrimitive && !targetReturnType->IsEnum && !targetReturnType->IsInterface) {
if (targetReturnType == System::String::typeid) {
return result;
}

if (targetReturnType == REFrameworkNET::ManagedObject::typeid) {
return result;
}

if (targetReturnType == REFrameworkNET::NativeObject::typeid) {
return result;
}

if (targetReturnType == REFrameworkNET::TypeDefinition::typeid) {
return result;
}

if (targetReturnType == REFrameworkNET::Method::typeid) {
return result;
}

if (targetReturnType == REFrameworkNET::Field::typeid) {
return result;
}
}

if (targetReturnType->IsEnum) {
auto underlyingType = targetReturnType->GetEnumUnderlyingType();

if (underlyingType != nullptr) {
auto underlyingResult = System::Convert::ChangeType(result, underlyingType);
return System::Enum::ToObject(targetReturnType, underlyingResult);
}
}

if (!targetReturnType->IsPrimitive && targetReturnType->IsInterface) {
auto iobjectResult = dynamic_cast<REFrameworkNET::IObject^>(result);

if (iobjectResult != nullptr && targetReturnType->IsInterface) {
// Caching mechanism to prevent creating multiple proxies for the same object and type so we dont stress the GC
if (auto existingProxy = iobjectResult->GetProxy(targetReturnType); existingProxy != nullptr) {
return existingProxy;
}

auto proxy = System::Reflection::DispatchProxy::Create(targetReturnType, Proxy<DummyInterface^, IObject^>::typeid->GetGenericTypeDefinition()->MakeGenericType(targetReturnType, result->GetType()));
((IProxy^)proxy)->SetInstance(iobjectResult);

if (auto unified = dynamic_cast<REFrameworkNET::UnifiedObject^>(iobjectResult); unified != nullptr) {
unified->AddProxy(targetReturnType, (IProxy^)proxy);
}

result = proxy;
return result;
}
}

return result;
}
}
1 change: 1 addition & 0 deletions csharp-api/REFrameworkNET/Utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ namespace REFrameworkNET {
ref class Utility {
public:
static System::Object^ BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInvoke);
static System::Object^ TranslateBoxedData(System::Type^ targetReturnType, System::Object^ boxedData);
};
}
19 changes: 16 additions & 3 deletions csharp-api/test/Test/Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,26 @@ public static void Entry() {
var meshes = via.SceneManager.get_MainScene().findComponents(via.render.Mesh.REFType.RuntimeType.As<_System.Type>());
//var range = via.RangeI.REFType.CreateInstance(0).As<via.RangeI>();
var range = REFrameworkNET.ValueType.New<via.RangeI>();
range.setMinMax(0, 10);
var testVec = REFrameworkNET.ValueType.New<via.vec3>();
System.Console.WriteLine("Test vec x: " + testVec.x);
System.Console.WriteLine("Test vec x before: " + testVec.x);
testVec.x = 1.0f;
System.Console.WriteLine("Test vec x: " + testVec.x);
testVec.y = 2.0f;
testVec.z = 3.0f;
System.Console.WriteLine("Test vec x after: " + testVec.x);
System.Console.WriteLine("Test vec y after: " + testVec.y);
System.Console.WriteLine("Test vec z after: " + testVec.z);

var axisXStatic = via.vec3.AxisX;
var axisYStatic = via.vec3.AxisY;
var axisZStatic = via.vec3.AxisZ;

System.Console.WriteLine("Axis X: " + axisXStatic.x + " " + axisXStatic.y + " " + axisXStatic.z);
System.Console.WriteLine("Axis Y: " + axisYStatic.x + " " + axisYStatic.y + " " + axisYStatic.z);
System.Console.WriteLine("Axis Z: " + axisZStatic.x + " " + axisZStatic.y + " " + axisZStatic.z);

//testVec[0] = 1.0f;
// print min max to test if this works
range.setMinMax(1, 10);
REFrameworkNET.API.LogInfo("Range min: " + range.getMin().ToString());
REFrameworkNET.API.LogInfo("Range max: " + range.getMax().ToString());
for (int i = 0; i < meshes.get_Length(); i++) {
Expand Down

0 comments on commit 695c1de

Please sign in to comment.