CesiumGS / cesium-unity

Bringing the 3D geospatial ecosystem to Unity
https://cesium.com/platform/cesium-for-unity/
Apache License 2.0
358 stars 83 forks source link

Better handling of Generic functions in Reinterop #379

Open j9liu opened 1 year ago

j9liu commented 1 year ago

While working on the planned 3D tiles 1.1 upgrade for metadata in Unity, I wondered if we could integrate Generics in any way to simplify the API. In particular, I thought it might be useful to have a CesiumPropertyTableProperty.GetAs<T> function, to avoid the individual GetBoolean, GetString, etc. calls we have to write for every possible type.

The idea is to implement the GetAs<T> on the C++ side with templating. Then, in ConfigureReinterop, we can list every single type of T that we expect to handle. Currently, Reinterop takes the generic function, and turns it into a template on the C++ side:

template <typename T>
  T GetAs(const T& value) const;

Then, it has a bunch of different calls matching the expected types, and it specializes the template in the .cpp file. Aside from an erroneous #include <T.h>, it's actually possible to compile the native code with a template<typename T> GetAs<T> C++ implementation. However...it doesn't completely work. Unity complains on the C# about CesiumPropertyTableProperty-generated.cs, because it generates the partial method like so:

        public partial T GetAs(T value)
        {
            unsafe
            {
                if (this._implementation == null || this._implementation.IsInvalid)
                    throw new NotImplementedException("The native implementation is missing so GetAs cannot be invoked. This may be caused by a missing call to CreateImplementation in one of your constructors, or it may be that the entire native implementation shared library is missing or out of date.");
                var result = DotNet_CesiumForUnity_CesiumPropertyTableProperty_GetAs(Reinterop.ObjectHandleUtility.CreateHandle(this), _implementation, value);
                return result;
            }
        }

As @kring noted:

So the immediate problem is that it's not declared as a generic method at all.
But the bigger problem is that we can't write a single implementation of this method that works across types.

Ultimately we decided it's better to have the individual Get___ functions for usability reasons outside of Reinterop. But a solution that @kring proposed, if we need it in the future:

It's not totally impossible. You can use a generic class with a static field as a kind of map with C# generics. something like:

class TypeMapThingo<T> {
static int SomeValue;
}
TypeMapThingo<int>.SomeValue = 4;
TypeMapThing<float>.SomeValue = 5;

so in this case SomeValue could be a delegate that is used to dispatch to the correct C++ function pointer. But that'd definitely takes some changes to Reinterop