Dynamically Implementing Interfaces at Runtime
Good day all,
For a modding framework refactor/rework, we are implementing interface-based events, which can be defined by mods and will be loaded at runtime as well, ie:
and an event publisher will follow a pattern as follows:
The issue we have is that we support
Lua
scripting via MoonSharp
, which includes events. This was here before and we have the legacy API which must be supported as follows:
So, I've been investigating using some combination of Linq.Expression
and ExpandoObject
or dynamic
to dynamically construct a runtime proxy type that implements the interface but searching has been kind of a pain because all major examples are for either .NET Framework
and don't work in .NET Core, or they're for Mocking/Testing and are answered using Moq
, Castle
or some other testing-targeted framework.
I'd like to avoid having to Emit
an IL method dynamically. We have some of this already for dynamic hooking classes and maintain it has been a growing pain.
What are my options here? Because this is my goal in pseudo code:
Anyways, any help is appreciated.15 Replies
You shouldn't do this intrusively like that since you have that legacy. Decouple events from interfaces. The new way should just be an option of wiring the handlers. Also, I wouldn't do this automatically. I'd make this behavior opt-in so people could keep wiring their stuff manually if they want to.
To me the best solution would be to make the underlying handlers you actually store and execute more generic
So lua could wire to that directly
But yeah the way tou have it is tough. You probably should let your system call the handlers and forward the args
Another option is to source generate a wrapper class that calls a lua handler for each interface and have all mods provide that
Then find it with reflection
It does require having the client adjust their build
The old API for manually registerring to events is still completely supported, the events interface API is additional functionality.
The problem is the old API makes all events implement either
Action<object[]>
or Func<object, object[]>
as the delegate type.
so there's no static typing support nor more complex behaviour without casting and it's entire based on reading the API docs, which weren't that well maintained, or the code.
My goal was to have a wrapper/container class that would contain the actual LuaMethod delegate and implement the interface, then redirect the call to either the Action
or Func
handler based on signature.the best way is to source generate on each client
or create a runtime wrapper, which you said you don't want to do
or rethink your design
Well the runtime wrapper would probably be dynamic assembly IL emission.
And the problem with "wizardry" in community modding frameworks is they are impossible to maintain once the "wizard" leaves the project.
and curses get inserted into the spells via PRs
e.g. having the method have the same name for each event and having it derive from a generic interface, which might not be optimal from the static typing perspective
source generation is pretty easy to integrate
you just include a package
I have used it at work. The issue is most of the modding community uses
Lua
and/or their IDE of choice is either Notepad++ or VSCode :BaroDev:
Oh yeah
forgot to mention the best technical debt
The old system until recently only supported C# in the form of source files included in the package and compiled at runtime.
Anyways, ideally, everyone would be in Cs land but Cs was basically addded later.
I will rethink my approach thoughsounds like lots of fun
Worst case is that these systems remain separate (since we use the new event system internally).
I'd love to work there
lookup LuaCsForBarotrauma lel
or if you mean actual job, source gen because web dev work.
I'm surprised that there isn't a cleaner way to forward a delegate call to a generic
object[]
array sigwdym here?
asp net, source gen on backend
We might be misunderstanding each other, I just said that working on this modding integration thing in a game with lots of tech debt where you have to support legacy sounds like fun
No sarcasm
I mean, I am enjoying working on it. However, my primary motivation for joining the team was to roll my add-on toolkit into it lol.
I've learned a lot while doing it so far.
You mentioned wanting to avoid emitting due to maintainability concerns. Also SGs would require recompilation, which you say some users won't be able/willing to do.
You could emit class implementations dynamically not using
Emit
but roslyn, i.e. dynamically compiling source code that you can stitch together at runtime, then loading that new assembly to get the type.
This way, no "wizardry" concerning emitting would be needed, think string manipulation and some roslyn api calls.
Maybe this is useful.