Add better support for Arrays in Variant for C# Scripting

This commit is contained in:
Wojciech Figat
2021-11-17 19:49:21 +01:00
parent 5447dc9e25
commit 649059eba1

View File

@@ -151,99 +151,102 @@ VariantType MUtils::UnboxVariantType(MonoType* monoType)
const auto& stdTypes = *StdTypesContainer::Instance(); const auto& stdTypes = *StdTypesContainer::Instance();
const auto klass = mono_type_get_class(monoType); const auto klass = mono_type_get_class(monoType);
// TODO: optimize this with switch(monoType->type) maybe? // Fast type detection for in-built types
if (klass == mono_get_void_class() || monoType->type == MONO_TYPE_VOID) switch (monoType->type)
{
case MONO_TYPE_VOID:
return VariantType(VariantType::Void); return VariantType(VariantType::Void);
if (klass == mono_get_boolean_class() || monoType->type == MONO_TYPE_BOOLEAN) case MONO_TYPE_BOOLEAN:
return VariantType(VariantType::Bool); return VariantType(VariantType::Bool);
if (klass == mono_get_byte_class() || monoType->type == MONO_TYPE_U1) case MONO_TYPE_I1:
return VariantType(VariantType::Int16); return VariantType(VariantType::Int16);
if (klass == mono_get_sbyte_class() || monoType->type == MONO_TYPE_I1) case MONO_TYPE_U1:
return VariantType(VariantType::Int16); return VariantType(VariantType::Int16);
if (klass == mono_get_int16_class() || monoType->type == MONO_TYPE_I2) case MONO_TYPE_I2:
return VariantType(VariantType::Int16); return VariantType(VariantType::Int16);
if (klass == mono_get_uint16_class() || monoType->type == MONO_TYPE_U2) case MONO_TYPE_U2:
return VariantType(VariantType::Uint16); return VariantType(VariantType::Uint16);
if (klass == mono_get_int32_class() || monoType->type == MONO_TYPE_I4) case MONO_TYPE_I4:
case MONO_TYPE_CHAR:
return VariantType(VariantType::Int); return VariantType(VariantType::Int);
if (klass == mono_get_uint32_class() || monoType->type == MONO_TYPE_U4) case MONO_TYPE_U4:
return VariantType(VariantType::Uint); return VariantType(VariantType::Uint);
if (klass == mono_get_int64_class() || monoType->type == MONO_TYPE_I8) case MONO_TYPE_I8:
return VariantType(VariantType::Int64); return VariantType(VariantType::Int64);
if (klass == mono_get_uint64_class() || monoType->type == MONO_TYPE_U8) case MONO_TYPE_U8:
return VariantType(VariantType::Uint64); return VariantType(VariantType::Uint64);
if (klass == mono_get_char_class() || monoType->type == MONO_TYPE_CHAR) case MONO_TYPE_R4:
return VariantType(VariantType::Int);
if (klass == mono_get_single_class() || monoType->type == MONO_TYPE_R4)
return VariantType(VariantType::Float); return VariantType(VariantType::Float);
if (klass == mono_get_double_class() || monoType->type == MONO_TYPE_R8) case MONO_TYPE_R8:
return VariantType(VariantType::Double); return VariantType(VariantType::Double);
if (klass == mono_get_string_class() || monoType->type == MONO_TYPE_STRING) case MONO_TYPE_STRING:
return VariantType(VariantType::String); return VariantType(VariantType::String);
if (klass == mono_get_intptr_class() || klass == mono_get_uintptr_class() || monoType->type == MONO_TYPE_PTR) case MONO_TYPE_PTR:
return VariantType(VariantType::Pointer); return VariantType(VariantType::Pointer);
if (monoType->type == MONO_TYPE_OBJECT) case MONO_TYPE_VALUETYPE:
if (klass == stdTypes.GuidClass->GetNative())
return VariantType(VariantType::Guid);
if (klass == stdTypes.TypeClass->GetNative())
return VariantType(VariantType::Typename);
if (klass == stdTypes.Vector2Class->GetNative())
return VariantType(VariantType::Vector2);
if (klass == stdTypes.Vector3Class->GetNative())
return VariantType(VariantType::Vector3);
if (klass == stdTypes.Vector4Class->GetNative())
return VariantType(VariantType::Vector4);
if (klass == stdTypes.ColorClass->GetNative())
return VariantType(VariantType::Color);
if (klass == stdTypes.BoundingBoxClass->GetNative())
return VariantType(VariantType::BoundingBox);
if (klass == stdTypes.QuaternionClass->GetNative())
return VariantType(VariantType::Quaternion);
if (klass == stdTypes.TransformClass->GetNative())
return VariantType(VariantType::Transform);
if (klass == stdTypes.BoundingSphereClass->GetNative())
return VariantType(VariantType::BoundingSphere);
if (klass == stdTypes.RectangleClass->GetNative())
return VariantType(VariantType::Rectangle);
if (klass == stdTypes.MatrixClass->GetNative())
return VariantType(VariantType::Matrix);
break;
case MONO_TYPE_OBJECT:
return VariantType(VariantType::ManagedObject); return VariantType(VariantType::ManagedObject);
if (klass == stdTypes.GuidClass->GetNative()) case MONO_TYPE_SZARRAY:
return VariantType(VariantType::Guid); if (klass == mono_array_class_get(mono_get_byte_class(), 1))
if (klass == stdTypes.TypeClass->GetNative()) return VariantType(VariantType::Blob);
return VariantType(VariantType::Typename); break;
if (klass == stdTypes.Vector2Class->GetNative()) }
return VariantType(VariantType::Vector2);
if (klass == stdTypes.Vector3Class->GetNative()) // Get actual typename for full type info
return VariantType(VariantType::Vector3); if (!klass)
if (klass == stdTypes.Vector4Class->GetNative()) return VariantType(VariantType::Null);
return VariantType(VariantType::Vector4); MString fullname;
if (klass == stdTypes.ColorClass->GetNative()) GetClassFullname(klass, fullname);
return VariantType(VariantType::Color); switch (monoType->type)
if (klass == stdTypes.BoundingBoxClass->GetNative()) {
return VariantType(VariantType::BoundingBox); case MONO_TYPE_SZARRAY:
if (klass == stdTypes.QuaternionClass->GetNative()) case MONO_TYPE_ARRAY:
return VariantType(VariantType::Quaternion); return VariantType(VariantType::Array, fullname);
if (klass == stdTypes.TransformClass->GetNative()) case MONO_TYPE_ENUM:
return VariantType(VariantType::Transform); return VariantType(VariantType::Enum, fullname);
if (klass == stdTypes.BoundingSphereClass->GetNative()) case MONO_TYPE_VALUETYPE:
return VariantType(VariantType::BoundingSphere); return VariantType(VariantType::Structure, fullname);
if (klass == stdTypes.RectangleClass->GetNative()) }
return VariantType(VariantType::Rectangle);
if (klass == stdTypes.MatrixClass->GetNative())
return VariantType(VariantType::Matrix);
CHECK_RETURN(klass, VariantType(VariantType::Null));
if (mono_class_is_subclass_of(klass, Asset::GetStaticClass()->GetNative(), false) != 0) if (mono_class_is_subclass_of(klass, Asset::GetStaticClass()->GetNative(), false) != 0)
{ {
if (klass == Asset::GetStaticClass()->GetNative()) if (klass == Asset::GetStaticClass()->GetNative())
return VariantType(VariantType::Asset); return VariantType(VariantType::Asset);
StringAnsi fullname;
GetClassFullname(klass, fullname);
return VariantType(VariantType::Asset, fullname); return VariantType(VariantType::Asset, fullname);
} }
if (mono_class_is_subclass_of(klass, ScriptingObject::GetStaticClass()->GetNative(), false) != 0) if (mono_class_is_subclass_of(klass, ScriptingObject::GetStaticClass()->GetNative(), false) != 0)
{ {
if (klass == ScriptingObject::GetStaticClass()->GetNative()) if (klass == ScriptingObject::GetStaticClass()->GetNative())
return VariantType(VariantType::Object); return VariantType(VariantType::Object);
StringAnsi fullname;
GetClassFullname(klass, fullname);
return VariantType(VariantType::Object, fullname); return VariantType(VariantType::Object, fullname);
} }
if (mono_class_is_enum(klass))
{
MString fullname;
GetClassFullname(klass, fullname);
return VariantType(VariantType::Enum, fullname);
}
if (klass == mono_array_class_get(mono_get_byte_class(), 1))
return VariantType(VariantType::Blob);
if (monoType->type == MONO_TYPE_SZARRAY || monoType->type == MONO_TYPE_ARRAY)
{
MString fullname;
GetClassFullname(klass, fullname);
return VariantType(VariantType::Array, fullname);
}
// TODO: support any dictionary unboxing // TODO: support any dictionary unboxing
// TODO: support any structure unboxing
// TODO: unbox other types as generic ManagedObject type LOG(Error, "Invalid managed type to unbox {0}", String(fullname));
LOG(Error, "Invalid managed type to unbox {0}", String(GetClassFullname(klass)));
return VariantType(); return VariantType();
} }
@@ -329,6 +332,7 @@ Variant MUtils::UnboxVariant(MonoObject* value)
GetClassFullname(klass, fullname); GetClassFullname(klass, fullname);
Variant v; Variant v;
v.Type = MoveTemp(VariantType(VariantType::Enum, fullname)); v.Type = MoveTemp(VariantType(VariantType::Enum, fullname));
// TODO: what about 64-bit enum? use enum size with memcpy
v.AsUint64 = *static_cast<uint32*>(mono_object_unbox(value)); v.AsUint64 = *static_cast<uint32*>(mono_object_unbox(value));
return v; return v;
} }
@@ -365,18 +369,97 @@ Variant MUtils::UnboxVariant(MonoObject* value)
v.SetType(MoveTemp(VariantType(VariantType::Array, fullname))); v.SetType(MoveTemp(VariantType(VariantType::Array, fullname)));
auto& array = v.AsArray(); auto& array = v.AsArray();
array.Resize((int32)mono_array_length((MonoArray*)value)); array.Resize((int32)mono_array_length((MonoArray*)value));
if (mono_class_is_valuetype(monoType->data.array->eklass)) const StringAnsiView elementTypename(*fullname, fullname.Length() - 2);
MonoClass* elementClass = monoType->data.array->eklass;
uint32_t elementAlign;
const int32 elementSize = mono_class_value_size(elementClass, &elementAlign);
if (mono_class_is_enum(elementClass))
{ {
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(StringAnsiView(*fullname, fullname.Length() - 2)); // Array of Enums
if (typeHandle) for (int32 i = 0; i < array.Count(); i++)
{ {
// TODO: Unboxing value-type Variant array array[i].SetType(VariantType(VariantType::Enum, elementTypename));
MISSING_CODE("Unboxing value-type Variant array"); Platform::MemoryCopy(&array[i].AsUint64, mono_array_addr_with_size((MonoArray*)value, elementSize, i), elementSize);
}
}
else if (mono_class_is_valuetype(elementClass))
{
// Array of Structures
VariantType elementType = UnboxVariantType(mono_class_get_type(elementClass));
switch (elementType.Type)
{
case VariantType::Bool:
case VariantType::Int:
case VariantType::Uint:
case VariantType::Int64:
case VariantType::Uint64:
case VariantType::Float:
case VariantType::Double:
case VariantType::Vector2:
case VariantType::Vector3:
case VariantType::Vector4:
case VariantType::Color:
case VariantType::Guid:
case VariantType::BoundingSphere:
case VariantType::Quaternion:
case VariantType::Rectangle:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Int16:
case VariantType::Uint16:
// Optimized unboxing of raw data type
for (int32 i = 0; i < array.Count(); i++)
{
auto& a = array[i];
a.SetType(elementType);
Platform::MemoryCopy(&a.AsData, mono_array_addr_with_size((MonoArray*)value, elementSize, i), elementSize);
}
break;
case VariantType::BoundingBox:
case VariantType::Transform:
case VariantType::Ray:
case VariantType::Matrix:
// Optimized unboxing of raw data type
for (int32 i = 0; i < array.Count(); i++)
{
auto& a = array[i];
a.SetType(elementType);
Platform::MemoryCopy(a.AsBlob.Data, mono_array_addr_with_size((MonoArray*)value, elementSize, i), elementSize);
}
break;
case VariantType::Structure:
{
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(elementType.TypeName);
if (typeHandle)
{
// Unbox array of structures
const ScriptingType& type = typeHandle.GetType();
ASSERT(type.Type == ScriptingTypes::Structure);
// TODO: optimize this for large arrays to prevent multiple AllocStructure calls in Variant::SetType by using computed struct type
for (int32 i = 0; i < array.Count(); i++)
{
auto& a = array[i];
a.SetType(elementType);
void* managed = mono_array_addr_with_size((MonoArray*)value, elementSize, i);
// TODO: optimize structures unboxing to not require MonoObject* but raw managed value data to prevent additional boxing here
MonoObject* boxed = mono_object_new(mono_domain_get(), elementClass);
Platform::MemoryCopy(mono_object_unbox(boxed), managed, elementSize);
type.Struct.Unbox(a.AsBlob.Data, boxed);
}
break;
}
LOG(Error, "Invalid type to unbox {0}", v.Type);
break;
}
default:
LOG(Error, "Invalid type to unbox {0}", v.Type);
break;
} }
LOG(Error, "Invalid type to unbox {0}", v.Type);
} }
else else
{ {
// Array of Objects
for (int32 i = 0; i < array.Count(); i++) for (int32 i = 0; i < array.Count(); i++)
array[i] = UnboxVariant(mono_array_get((MonoArray*)value, MonoObject*, i)); array[i] = UnboxVariant(mono_array_get((MonoArray*)value, MonoObject*, i));
} }
@@ -449,31 +532,102 @@ MonoObject* MUtils::BoxVariant(const Variant& value)
return value.AsAsset ? value.AsAsset->GetOrCreateManagedInstance() : nullptr; return value.AsAsset ? value.AsAsset->GetOrCreateManagedInstance() : nullptr;
case VariantType::Array: case VariantType::Array:
{ {
MonoArray* managed = nullptr; MonoArray* managed;
const auto& array = value.AsArray(); const auto& array = value.AsArray();
if (value.Type.TypeName) if (value.Type.TypeName)
{ {
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(StringAnsiView(value.Type.TypeName, StringUtils::Length(value.Type.TypeName) - 2)); const StringAnsiView elementTypename(value.Type.TypeName, StringUtils::Length(value.Type.TypeName) - 2);
if (typeHandle) const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(elementTypename);
MonoClass* elementClass;
if (typeHandle && typeHandle.GetType().ManagedClass)
elementClass = typeHandle.GetType().ManagedClass->GetNative();
else
elementClass = Scripting::FindClassNative(elementTypename);
if (!elementClass)
{ {
const ScriptingType& type = typeHandle.GetType(); LOG(Error, "Invalid type to box {0}", value.Type);
if (type.Type == ScriptingTypes::Script || type.Type == ScriptingTypes::Class || (type.ManagedClass && !mono_class_is_valuetype(type.ManagedClass->GetNative()))) return nullptr;
}
uint32_t elementAlign;
const int32 elementSize = mono_class_value_size(elementClass, &elementAlign);
managed = mono_array_new(mono_domain_get(), elementClass, array.Count());
if (mono_class_is_enum(elementClass))
{
// Array of Enums
for (int32 i = 0; i < array.Count(); i++)
{ {
managed = mono_array_new(mono_domain_get(), type.ManagedClass->GetNative(), array.Count()); auto data = (uint64)array[i];
for (int32 i = 0; i < array.Count(); i++) Platform::MemoryCopy(mono_array_addr_with_size(managed, elementSize, i), &data, elementSize);
mono_array_setref(managed, i, BoxVariant(array[i]));
} }
else }
else if (mono_class_is_valuetype(elementClass))
{
// Array of Structures
VariantType elementType = UnboxVariantType(mono_class_get_type(elementClass));
switch (elementType.Type)
{ {
// TODO: Boxing value-type Variant array case VariantType::Bool:
MISSING_CODE("Boxing value-type Variant array"); case VariantType::Int:
case VariantType::Uint:
case VariantType::Int64:
case VariantType::Uint64:
case VariantType::Float:
case VariantType::Double:
case VariantType::Vector2:
case VariantType::Vector3:
case VariantType::Vector4:
case VariantType::Color:
case VariantType::Guid:
case VariantType::BoundingSphere:
case VariantType::Quaternion:
case VariantType::Rectangle:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Int16:
case VariantType::Uint16:
// Optimized boxing of raw data type
for (int32 i = 0; i < array.Count(); i++)
Platform::MemoryCopy(mono_array_addr_with_size(managed, elementSize, i), &array[i].AsData, elementSize);
break;
case VariantType::BoundingBox:
case VariantType::Transform:
case VariantType::Ray:
case VariantType::Matrix:
// Optimized boxing of raw data type
for (int32 i = 0; i < array.Count(); i++)
Platform::MemoryCopy(mono_array_addr_with_size(managed, elementSize, i), array[i].AsBlob.Data, elementSize);
break;
case VariantType::Structure:
if (typeHandle)
{
const ScriptingType& type = typeHandle.GetType();
ASSERT(type.Type == ScriptingTypes::Structure);
for (int32 i = 0; i < array.Count(); i++)
{
// TODO: optimize structures boxing to not return MonoObject* but use raw managed object to prevent additional boxing here
MonoObject* boxed = type.Struct.Box(array[i].AsBlob.Data);
Platform::MemoryCopy(mono_array_addr_with_size(managed, elementSize, i), mono_object_unbox(boxed), elementSize);
}
break;
}
LOG(Error, "Invalid type to box {0}", value.Type);
break;
default:
LOG(Error, "Invalid type to box {0}", value.Type);
break;
} }
} }
else else
LOG(Error, "Invalid type to box {0}", value.Type); {
// Array of Objects
for (int32 i = 0; i < array.Count(); i++)
mono_array_setref(managed, i, BoxVariant(array[i]));
}
} }
else else
{ {
// object[]
managed = mono_array_new(mono_domain_get(), mono_get_object_class(), array.Count()); managed = mono_array_new(mono_domain_get(), mono_get_object_class(), array.Count());
for (int32 i = 0; i < array.Count(); i++) for (int32 i = 0; i < array.Count(); i++)
mono_array_setref(managed, i, BoxVariant(array[i])); mono_array_setref(managed, i, BoxVariant(array[i]));
@@ -503,7 +657,7 @@ MonoObject* MUtils::BoxVariant(const Variant& value)
return nullptr; return nullptr;
} }
case VariantType::ManagedObject: case VariantType::ManagedObject:
return (MonoObject*)value; return value.AsUint ? mono_gchandle_get_target(value.AsUint) : nullptr;
case VariantType::Typename: case VariantType::Typename:
{ {
const auto klass = Scripting::FindClassNative((StringAnsiView)value); const auto klass = Scripting::FindClassNative((StringAnsiView)value);
@@ -635,6 +789,13 @@ MonoClass* MUtils::GetClass(const VariantType& value)
case VariantType::Matrix: case VariantType::Matrix:
return stdTypes.MatrixClass->GetNative(); return stdTypes.MatrixClass->GetNative();
case VariantType::Array: case VariantType::Array:
if (value.TypeName)
{
const StringAnsiView elementTypename(value.TypeName, StringUtils::Length(value.TypeName) - 2);
mclass = Scripting::FindClass(elementTypename);
if (mclass)
return mono_array_class_get(mclass->GetNative(), 1);
}
return mono_array_class_get(mono_get_object_class(), 1); return mono_array_class_get(mono_get_object_class(), 1);
case VariantType::ManagedObject: case VariantType::ManagedObject:
return mono_get_object_class(); return mono_get_object_class();
@@ -791,8 +952,6 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, const MType& type, bool& fa
return &value.AsDouble; return &value.AsDouble;
case MONO_TYPE_STRING: case MONO_TYPE_STRING:
return MUtils::ToString((StringView)value); return MUtils::ToString((StringView)value);
// TODO: MONO_TYPE_PTR
// TODO: MONO_TYPE_BYREF
case MONO_TYPE_VALUETYPE: case MONO_TYPE_VALUETYPE:
{ {
MonoClass* klass = type.GetNative()->data.klass; MonoClass* klass = type.GetNative()->data.klass;
@@ -873,19 +1032,18 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, const MType& type, bool& fa
object = nullptr; object = nullptr;
return object; return object;
} }
// TODO: MONO_TYPE_VAR
// TODO: MONO_TYPE_ARRAY
// TODO: MONO_TYPE_GENERICINST
// TODO: MONO_TYPE_TYPEDBYREF
case MONO_TYPE_OBJECT: case MONO_TYPE_OBJECT:
return BoxVariant(value); return BoxVariant(value);
case MONO_TYPE_SZARRAY: case MONO_TYPE_SZARRAY:
if (value.Type == VariantType::Array) case MONO_TYPE_ARRAY:
{ {
// TODO: box Variant array into C# Array if (value.Type.Type != VariantType::Array)
} return nullptr;
return nullptr; MonoObject* object = BoxVariant(value);
// TODO: MONO_TYPE_ENUM if (object && !mono_class_is_subclass_of(mono_object_get_class(object), mono_array_class_get(type.GetNative()->data.klass, 1), false))
object = nullptr;
return object;
}
default: default:
break; break;
} }