Map generic c# functions of registered types to lua functions#344
Conversation
…the generic arguments as starting parameters
|
Thanks for the contribution. Admittedly, I'm having a bit of trouble understanding the use case here. It looks like you're exposing a reference to the C# class as a global, then users have to know to obtain that C# class reference and pass it as a parameter, and that type has to match the type of a subsequent parameter etc. (wherever that generic is consumed in the list). Given that you need to manually expose types, and users have to know to pass those types, it doesn't seem very flexible / safe. This is also confusing because it seems to only support one generic parameter, and doesn't account for things like I can see why you may want something like this for your own convenience, but it doesn't really seem like the kind of thing that ought to live in MoonSharp itself. |
Yea, the usefulness heavily depends on how lua scripts are integrated in the application. If you only allow a very limited number of types and carefully providing selected functions only, this feature has almost no benefit, as you usually just provide a function with the otherwise-generic parameter as first parameter yourself, if you need that functionality. I.e. instead of providing
you could just do
The more your application treats lua as a "scripting against lots and lots of classes while minimizing the effort on the C# integration side", the better the benefit gets, as you can just re-use existing functions and don't have to introduce wrappers or proxy types all over the place. (And this is our use case ;) ). So the main benefit is to just be able to re-use existing classes that happen to add generic functions and not having to write wrapper functions yourself. Oh, another benefit is, that some generic functions can not easily be re-written as "Foobar(Type)", if they need to be generated differently by the C# compiler for different T's and/or because they are calling other generic functions on the T themself. To fix that would occur a manual, explicit reflection+MakeGenericMethod call in the wrapper function: Sure - it doesn't hurt that much if its only a dozen of those wrapper lines, but this gets old quickly.
Well, it doesn't have to be exposed as a global variable. That was just the easiest way to do it in a as-simple-as-possible test ;). But I thought using an userdata on "Type" is kind-of normal if you want to reference a type in lua? How else would you express a C# type in lua?
Yea.. I know. In the current form, it is "explicit-declaration-only". In the Foobar above (that has no parameters), you would also have to declare the concrete type in C#, but yes: the "even cooler" feature would be to support type deduction like this:
maps to just calling "obj:foobar(t)" in lua function and the type for T is deduced from "t"'s type. I might try on that feature later, but well... tbh.. I am not 100% sure how to tackle that immediately, robust and straight forward.. (I don't think C# brings any stock functionality to deduce types on a generic MethodInfo's. Roslyn probably does, but I am so NOT going to import a whole compiler DLL just for that feature :-D) IMHO it might still be beneficial to explicitly specify the generic type, even if MoonSharp would support type deduction. Because you can also do this in C# calls.
But what would be the alternative? IF you have C# code that would fit a scheme where you pass a Type parameter, how else would you model that? I mean.. sure: If you never even want to have a function that takes any System.Type, then you probably also never want to have a function exposed that are generic (because that is kindof semantically similar to "passing a type"). And again: the fact that I "manually" set the types in the test cases as global variables doesn't mean they always have to be manually set this way ;). The types could have been returned from other functions or obtained via another global function (or by an exposed get_type function). Hm.. thinking of it.. I could allow any userdata object to be passed instead of the type parameters and then just take its type. But in my opinion, that makes things a lot more confusing and vastly increases the mismatches or ambiguous calls.
Huh? I should support multiple generic arguments just fine, see test Interop_GenericMethods in UserDataMethodsTests.cs, line 740 calling GenericStaticDescribeTypes with two generic types. Did I made a mistake somewhere? (I started with only one argument for simplicity but then though: That's just unnecessary restricting and changed it later to support any number of generic arguments).
Well, it kindof does. It will fail with an exception when you try to create the concrete generic method passing the wrong type that does not fit the constraints. (Line 70 of GenericMethodMemberDescriptor.cs) But yes, the error message could be improved here to either parse the C# exception or do some manual constraint verification inside the Execute (but that would occur a runtime penalty, so...)
You think so? Hm.. I have to admit that I don't really got the overall place of where MoonSharp wants to live. I mean.. NLua clearly goes towards "here you have C#, just map everything as fast as possible and don't bother me with details". This PR would fit perfectly there. :-D MoonSharp indeed strongly discourages "binding of everything" and encourages dedicated, carefully constructed interfaces. But nevertheless, it still provides mechanisms to "broadly fast-bind lots of existing classes" (e.g. by providing IRegistrationPolicy where coders can easily decide to auto-binding whole sets of functions). Hence, I thought it might actually be a good fit for these scenarios. And if the class you expose does not have exposed generic functions anyway, the feature would not change anything either. |
|
My instinct is that an explicit boundary between C# and Lua is worth the boilerplate. You get a reviewable API surface, refactors don't silently break scripts, and edge cases like generic constraints or AOT/IL2CPP behaviour stop being the binding layer's problem. For the cases where you genuinely need a generic helper in Lua, a hand-written Foo(Type t, ...) wrapper feels like the right place to pay that cost. |
Sure.. that's certainly a sane design choice. All I suggest is, that we map generic functions in those types that are exposed to lua - instead of ignoring them. And my suggestion how to map them was, to pass their types as first parameter. That's really all. ;) I mean... MoonSharp has a TON of mapping logic to make it "easy" to declare C# functions and expose these to lua without forcing the scripters to write boilerplate. And its not clear to me where "the line is drawn". Some of the mapping features are quite intuitive, some are a bit weirder, some are forbidden for "reasons". It does map default arguments. It does map ref parameter, but turns the return value into a tuple. (might be because its unnatural and very hard to ensure that only variables are passed and then change the script environment to do a "more C# - natural" way). But honestly: MoonSharp could also decided to not support ref parameter at all. They can easily be wrapped in other functions that are "more normal" to read. It does not support generic functions with unbound arguments, although my suggested "mapping to a function taking the arguments as first parameter" seems to me a LOT more intuitive than the ref->tuple mapping.. but maybe just because I grew up with generics and C# instead of functional programming languages :D) It does not support pointers, for no obvious reasons to me except "because we don't think pointers are a good thing". Pointers could easily supported structurally. (And which btw, would be my next PR - if anyone is interested. Because IMO, these are crucial to get MoonSharp running with anything close to "acceptable performance" in a heavy Unity-ECS project for frequently called script functions running over lots of data - like ECS tables. In native lua (without all the code-gen tools), you are required to just have "void Fn(lua_state*)" as signature and do all the mapping of parameters yourself. I am glad for auto-mapping in MoonSharp. :D
Funny you mention that, because the support for bounded generics (i.e. a parameter "List<MyStruct>" to a function have exactly that problem, although they ARE supported by MoonSharp as auto-mapping. :-D But that is indeed a minus for my PR here, as providing any auto-mapping of generic functions might make naive end users forget about AOT. You still have the same problem as if you would use my "wrapper-line" above: in AOT scenarios, MakeGenericMethod() just doesn't cut it. You need a compiled explicit call somewhere. (So in that case, neither this PR nor the wrapper-"workaround" would help you) So yes, you could argue: WHY? but you could also argue: Why not? If its a feature that does not cost anything for people who don't use it and which just makes it more convenient for scripters, then why not? Where is the obvious line that I am missing for when convenience stops? And for projects, where you actually want to use lua as a "debug-mapping to whatever" with minimal effort, MoonSharp also has some (rightly discouraged for production code) features like the AutomaticRegistrationPolicy. For these, auto-mapping of generics is really nice. |
This allows users to register conversion on types like "MyClass<>".
…ect files and do not break into first lua line on attaching the debugger. Previously, the DAP debugger reported every file with the project filename, even if it existed on the machine. According to DAP, the debugger MUST ask for the source file, even if its included in a project, which forces VSCode and other DAP clients to always open a new temporary file (whose content is retrieved from the connected server) instead of opening the file from the project or local file system. Also, attaching a client was always breaking the execution. Most DAP clients (including VSCode) have an own option to break immediately, so that was not necessary and an annoying and unexpected debugging hickup.
NLua (and the old LuaInterface) allowed instances of types by using __call
on a userdata of the C# System.Type instead of calling an non-standard
"__new" method. This somewhat became a pseudo-standard even so that some IDE
highlighting tools like EmmyLua support this. Nowhere else "__new" is used.
Also, this implements the C# way of initializing fields by passing a
lua table as first parameter, e.g.
local s = MyStruct{ Field1 = 23, Field2 = "foo" }
Re-check tail and yield requests after CLR continuations and userdata index results, so requests to yield from inside index operators are handled before the bytecode execution continues. This allows C# types to yield from inside __index and __newindex methods.
This adds support for generic C# methods.
Generic method type arguments are passed from Lua as Type userdata arguments before other parameters. Basic overload resolution support against non-generic overloads and params arrays in generics.
I also added some tests.