Closed laper32 closed 8 months ago
and also S&Box, which is the only game what using C# for scripting.
FiveM (the native system is based on theirs)
You need to understand that the McMaster
plugin loader works like this:
And since each PluginContext
has their own AppDomain
(just like in FiveM, atleast in mono-v1, not sure about the newer one) we can't really access something from another AppDomain
without unwrapping the assembly again which is basically leads us to nowhere.
I'd either suggest how the exports are implemented in FiveM, or dependency injection in the HOST
so other plugins will be able to add their services, and dependant plugins get them using the provider from the host. (This idea is pretty skeptical as I'm not sure if that DI could even work as a shared type in the could work: https://github.com/SourceSharp/runtime/blob/master/Core/PluginManager.cs)McMaster
About splitting Core and API, I'm not sure why would it be needed in general. The way DI would work for normal interfaces are okay, but what about natives? I don't think there is any point on this.
We can embed other languages easily because of the native scripting system that is currently available as it has the unmanaged part done and can be generated to any language mostly (with some prework)
But what will happen if css updated? Well, it may crash, right? Then that's the problem.
Plugins can set what version of css they require to run to avoid crashes
I think dependency injection in the host is a good idea, I would like to also add a bridge interface that allows plugins to register their own services to a shared container. The biggest problem is hot reloading in such scenario when most DI containers would not handle dynamic re-loading of the application container
Perhaps we need a definition of: What is native? By experience, it follows:
Now, looking into the definition of native, current implementation, and our experience, we can find a fun fact that: For detour/reverse engineering part, managed side are all using functions/infrastructures from unmanaged side, no matter what it is (Detoured function/event, Hook tools, etc).
So, based on it, we can make a derived result: No matter how managed side changed, the native inside unmanaged side will not be affected.
Also,
Additionally:
Why I prefer separate API and core? Well, in my opinion, it could:
.Core
is the runtime for applications, and it's also be a bridge, between 'application' and 'engine'.
As I stated before, it connect between application and engine, etc. And also, I think something like supporting new language should be handled in the .Core
part, because support new language also means that we need to handle data synchronize between different languages, and even between different VM.
But, indeed, these can be handled with plugin, but I'm not sure whether it will cause performance loss, further profiling required.For the plugin system, I've noticed a problem that: binary files, config files, data files(incl. SQL .db, some plugins generated cache, etc) are completely messed up. Well, despite these can be user-defined, but in general, user won't. Mostly, they will keep everything in a single folder if you don't pre classify.
In fact, this comes out a greater problem: What is plugin system?
From wikipedia, the definition is:
In computing, a plug-in (or plugin, add-in, addin, add-on, or addon) is a software component that adds a specific feature to an existing computer program. When a program supports plug-ins, it enables customization.
A theme or skin is a preset package containing additional or changed graphical appearance details, achieved by the use of a graphical user interface (GUI) that can be applied to specific software and websites to suit the purpose, topic, or tastes of different users to customize the look and feel of a piece of computer software or an operating system front-end GUI (and window managers).
With a single phrase, it could be: Provide a method to extend the software itself. Different people will have different implementation. Game engine like Unreal (what I'm familiar with), the plugin can be:
Why a plugin provides so many features? If we focusing on game industry, we can find that a game engine should find a way to adapt as many game type as possible. If we jump out of game industry, we could find that different people have different demands, some of them even contradict.
And remember that Unreal's plugin system has been developed many years, we are now just beginning.
In fact, we have already implemented some of these features (e.g.: Spearate all plugins into their own directories), this is a good beginning, we just need to iterate it.
Now, we have been discussed too much. What should we do in the future? Better step-by-step.
.dll
, which does not provide additional information, such as PluginAuthor, etc.However, we still need to provide a method to declare plugin infromation, which are all in Plugin.toml
I'm prefering use toml as configuration file, but this can be changed as any config type.
The config file identifies the plugin name, author, description, version, modules need to load, etc.
Noting that for all plugins, several directories need to be reserved. Plugin cannot override its usage. These directories are:
For example, someone provided a new language, but provided as a plugin. It marks
scripts/this_language
as the scan directory. But no matter how the directory defines, nobody can definebin
as their own specific usage directory, even ifconfig
andgamedata
.
The plugin can also have dependencies, which is common for sub-plugins.
Also, plugin can be disabled by modify Plugin.toml
's field what we provided, or just simply drag it into disabled
.
For logging, we should do such things:
However, during the experience of sourcemod, we will meet a problem when you are running something with multithread:
Based on above, that's why I will put logging into engine layer, which is really necessary.
Now, we have spdlog, it looks like we just need to write a wrapper, then that's all. (Perhaps? review required)
The logging format?
Generally, it will be [Timestamp:yyyy/mm/dd HH:MM:SS] [LogLevel] [Logger] [FullFunctionSymbol] Message
Then you will find that the logger name is the specific space of its name.
The plugin will be: [Timestamp:yyyy/mm/dd HH:MM:SS] [LogLevel] [PluginName] [FullFunctionSymbol] Message
Example:
[2023/11/14 10:41:35] [Error] [Warcraft] [Warcraft::WarcraftPlugin::Load(154)] Something fucked up when starting the game! Exception: Exception thrown when handling blabalbla... at blablabla.blablabla ... (InvocationChainEnd)
The managed side: [Timestamp:yyyy/mm/dd HH:MM:SS] [LogLevel] [Runtime] [FullFunctionSymbol] Message
Example:
[2023/11/14 10:41:35] [Critical] [Runtime] [CounterStrikeSharp::API::Helpers::LoadAllPlugins(75)] Fatal error! Exception: NativeInvoke does not found! at blablabla.blablabla ... (InvocationChainEnd)
The unmanaged side: [Timestamp:yyyy/mm/dd HH:MM:SS] [LogLevel] [Core] [FullFunctionSymbol] Message
Example:
[2023/11/14 10:41:35] [Info] [Core] [counterstrikesharp::DotNetManager::Initialize(45)] Loading fxr...
[Timestamp:yyyy/mm/dd HH:MM:SS] [LogLevel] [FullFunctionSymbol] [PluginName] Message
with huge respect towards the cooking master (you laper32) that format gives me ancient java vibes with anxiety, but I like the NativeInvoke
related exception handling
[Timestamp:yyyy/mm/dd HH:MM:SS] [LogLevel] [FullFunctionSymbol] [PluginName] Message
with huge respect towards the cooking master (you laper32) that format gives me ancient java vibes with anxiety, but I like the
NativeInvoke
related exception handling
windows support delayed half a day in fact since NativeInvoke fucked up because of extern rule is different between lin and win, and also because of exception message does not log fully at that time lol.
About error handling:
No matter how many layers do we have in a big project, the error handling is still a headache problem. It need to:
Accelerator and FiveM is using google's breakpad to save the problem what I've stated above.
But, we still need to:
If these problem solved, then everything will be happy.
Also, the growing speed is incridible fast, I think it's time to think a better name to rename CounterStrikeSharp
.
Also, the growing speed is incridible fast, I think it's time to think a better name to rename
CounterStrikeSharp
.
CounterStrikeSharp is fine
Instead of shared data between app domains, I'd better call it: The whole design of
CounterStrikeSharp
.You could find that
shared data between app domains
is the subset ofthe whole design of 'CounterStrikeSharp'
.Why we discuss it? Well, this project,
CounterStrikeSharp
, will be renamed to a better name in the future, and it will directly affect the future of the project, and the future of the whole ecosystem.Any progress will be updated in this issue.
For now, yes, we are now using C# for scripting, but if I remember correctly, one of the aims is: any language what supports the format can be integrated easily and can also share data between different languages.
This requires us to make well-designed architecture. Generally, this MUST lead to a result: We are doing an engine, a tiny Operating System, in fact.
If we want to do it, Unreal, Unity, etc. must be important references, and also S&Box, which is the only game what using C# for scripting in Source 2.
Now, back to our question: How many layers should we have?
So, we have discussed a lot, what should it look like? For distributed folder layout:
where:
Plugin.toml
For implementation, in my opinion, we should:
Thus, what can we do?
We use the current version of
css
as an example.The current version of
css
's layer is:Currently, for the managed side, we have only
CounterStrikeSharp.API.dll
, which contains all implementations and interfaces.Indeed, it is a single file, easy to develop, and easy to take. People will only take care of the API.
But what will happen if css updated? Well, it may crash, right? Then that's the problem.
So we need to separate the implementation and interface, I will call them:
CounterStrikeSharp.Core.dll
CounterStrikeSharp.API.dll
Then, no matter how implementation changed, it won't affect the interface. The Plugin will only invoke the API, and will not take care of how this interface is implemented.
Also, the
CounterStrikeSharp.Core.dll
also acts as a runtime of application and unmanaged side.On the unmanaged side, as I stated previously, it interacts with the game engine, provide reversed-engineering stuffs, etc., and this is exactly what is doing.
OK, you may say: "This is too abstract, I want to see the sample code!", now here we go. For Core, as I said, interacts with engine layer and interface layer, it is used to initialize the scripting runtime.
the
CounterStrikeSharp.API.dll
, provides the API exposed from .Core, and also wraps APIs from engine layers.