Open manio143 opened 3 years ago
I wonder if this work would still be useful if we decide to switch editor in the future?
I wonder if this work would still be useful if we decide to switch editor in the future?
Yes, to an extent. There are changes around the package management system which are shared between the asset compiler and the editor - this is the entry point for loading plugins. If we do end up rewriting the editor, it will be up to the owner of a plugin to migrate their implementation onto the new version of presentation classes. But I suspect a good chunk of plugins will be only focused around {runtime component, asset, asset compiler} as the editor part is the one that takes a bit more time. Also if people do start to author plugins it will be easier to migrate onto new UI (and for the designers of the new editor to see which features are desired by plugin authors), then to wait with enabling plugins until after the potential switch.
@manio143 I do think that a very large part of the use-case, if not most, will fall into level-design (augmenting the scene view, procedural scene composition, tools for blocking out levels, etc.) so working on passing editor input to the user, having a way to draw things on screen and in tool windows and interacting with the live scene view. Those things are intrinsically tied to the UI system. I might be wrong though so if some of you guys could chime in with what you would do with this plugin system that would help.
Or we could use a different UI system for plugins specifically which we would migrate the rest of the editor to later down the line ?
For me the number one thing is the custom data assets - as you remember I worked on integrating something into Stride, but I had to do a lot not so nice changes to the internals of data serializers to handle generic classes and got hung up on the asset reloading on assembly reload - this is something that can be leveraged with the plugin system and roslyn code generators.
But yes, I have to agree that a large number of highly reusable plugins would attempt to make certain in-editor tasks easier, thus getting tied to the UI. I don't exactly see how we could use a different UI system for just plugins. It depends on what the plugin author wants to achieve - custom property editors have to be written in XAML as they would be rendered in the property grid, but advanced gizmo's could even use Stride's in-game UI system for modifying components.
custom property editors have to be written in XAML as they would be rendered in the property grid
We could have a dock-able empty wpf element that another ui system renders to, I haven't investigated this yet but I'm sure it's feasible with more lightweight library like myra or imgui. Once/if we swap the editor ui we would have to swap the surface with whatever UI we end up using and it wouldn't break user UIs.
There is also the issue of giving users a way to change the scene, right now the only way to do so in engine is painful to use and extremely slow because of the multiple scenes synchronizing and serialization round-tripping the engine does on every changes, we might have to simplify that or gut it out before moving forward with scenes plugins.
Should there be an attribute for editor plugins discovery?
How the engine figures out which assembly is a plugin ? So we would ask of users to specify something like this in their plugin's csproj <AssemblyAttribute Include="Stride.Core.PluginAssembly"/>
and then test that attribute in engine or something to that effect, I'm sure xen2 has some insight on this.
It's not a bad idea, with the rendering part to be handled by another lib. I will have to think about how exactly the interface layer for the plugin creator would look like so that it's not too much pain to execute view model updates from the other UI lib.
Should there be an attribute for editor plugins discovery?
This was targetted at how a custom StrideAssetsPlugin
instance would be registered (should've worded it better) - in general we should try to make all things registered with attributes as this makes it often easier to discover in one place, rather than have the user call an explicit Add or Register method in their code.
The point you mentioned - we can have a custom [assembly: Stride.Core.Plugin]
attribute and read that off of a loaded assembly.
Thanks for the well thought summary/proposal!
You identified one of the main design decisions to take: how to express what is a design-time plugin and make it fit with NuGet with proper dependencies?
As you mentioned, one possibility that sounds quite straightforward for the user to understand is to have 2 user projects: 1 runtime project and 1 design project.
If we take this approach, there is likely an explicit dependency (ProjectReference): the design project depends on the runtime project (i.e. the runtime project defines the runtime class of a custom asset). However, there is also an implicit dependency: the runtime project depends on the design project for asset compilation.
Runtime project is compiled in two phases: code compile, then asset compile. We need somehow to teach the build system to compile this design project after runtime code is compiled (runtime DLL might be needed for ProjectReference) and before assets can be compiled (design DLL needed by asset compiler). Basically, it would go in this order when compiling a runtime project:
It can be done by either MSBuild or by our asset compiler (it will need some careful considerations to figure out which is best).
Of course, we could think of another way to structure the projects and how they depend on each other (i.e. introduce an additional project for shared code). Let me know if you have any ideas on that front.
One aspect of the engine that needs plug-in support would be the post process fx
Hi @manio143, Would you be prepared to summarize this awesome suggestion for the Epic grant proposal? personally I think this topic is in the top 3 of most needed features of Stride. It would be great if we had budget for someone to start addressing this.
I've written a short high level explanation https://hackmd.io/@Ed53RzBbTu2HT46rCM--rQ/Bk0ukH_Ct
wow that is really well formulated.
Hi. i have an idea about realization of this thing.
Workflow must looks like
AssemblyInfo
of plugins and analyze em.ICSharpDecompiler
https://github.com/icsharpcode/ILSpy and get syntax tree of code.AssemblyLoadContext
(ALC)No actually. Just a wrapper that use 2 under the hood. It tryes to get from MAIN DI. if cant tryes get from second. First one is persistant while second one is reloadable (can be rebuilded in runtime). Also its for security reasons. When you loaded plugin to ALC you register factory method in second DI. You dont directly instantiate it. So basically it will looks like
var gridViewers= discoveredPlugins.Select(/* select all plugin types that was inhereted from IGridViewer and has attribute [GridViwer] */);
foraech(Type pluginPart in gridViewers)
{
reloadableDI.Add/*lifetime*/<IGridViewer>(ConstructGridViewerFromType(pluginPart));
}
IGridViewer ConstructGridViewerFromType(IServiceProvider sp, Type type)
{
return ActivatorUtilities.Instantiate(sp,type);
}
Then in engine editor when we drawing a grid viewrs we
// or composite di
reloadableDi.GetServices<IGridViewer>().Select(/* define which one should be used */);
// or we can have still DI but made custom discovery that will knows what to request
I made simple restriction analyzer https://github.com/ParadiseFallen/CsharpScripting/blob/master/Scripting/Analyzers/RestrictionAnalyzer.cs
it checks source code on Environment.Exit
thing. Also it can be improved to use compilation and compare MethodInfo
things.
With this we can restrict malware plugins.
Also we can force users to made Factories
that will know how to construct and how to register ther plugins.
Then can get required services and instantiate em
i can work on it after docs also with this we can easelly made package manager
Stride.Package.VR - core project that contains all `VR` related thing
+
Stride.Package.VR.Toolkit - project that contains `UI` for game stuio. custom editors and functionality for working with `VR` in game studio. Has dependency on `Stride.Package.VR`
NOTE: naming can be changed but wanna kinda `standart`.
like use suffix `Toolkit` for game engine related things (editor related and dev only things)
also then it will be easy to add kinda
Stride.Package.2D.Tilemaps
Stride.Package.2D.Tilemaps.Toolkit
I think the key here is not to go too far too quickly - i.e. before we think about how to do plugin discovery on the GameStudio level and any UI around that we first need to handle the basics of defining an interface layer for the plugins, which is partly done by the existing StrideAssetsPlugin
and AssetCompilerAttribute
, and creating a mechanism to for them to be loaded.
In my RFC I mentioned describing plugins on a per project basis - adding a list of internal/external packages to the sdpkg
file - this makes sense from the standpoint of custom assets. However, it may be cumbersome for users to add the same package to each of their projects manually. It would be interesting to think about a way of adding plugins on the editor level (saved across project sessions) that only get appended to the project if their contents is actually used. They wouldn't need to be referenced by the project at all if they only supply editor tooling. Why would the project need to know about plugins? For one it needs to reference the runtime package of a plugin to compile against and then the asset compiler needs to be aware of the design time package to process custom assets.
On the Toolkit
suffix above - I personally prefer Design
suffix.
When it comes to code analysis of a plugin - performing decompilation and checking the APIs called during runtime will be expensive. I'm not sure that's something we can go for. This is a complex problem in general and from what I understand there's currently no built-in security features in the .NET runtime (AppDomains and CAS have been removed) and even earlier it wouldn't have been easy - see this discussion. This means that by downloading the plugin the user must trust the library's authors. Since we want to use NuGet infrastructure, the user should be able to see if the given plugin has been downloaded many times from NuGet.org and see a link to the owners website.
I think the first task to look into is how to load a DLL from a NuGet package into the editor and the asset compiler - I attempted this a while back but was very much unsuccessful. Stride already has a mechanism for resolving NuGet packages - that's being used in the GameStudio entrypoint in order to ensure all its dependencies are downloaded and available from the NuGet cache - but the way it has been designed was as run-once type of thing, so not sure it can be reused here.
Once we have an experimental way of sideloading packages, we can start looking into defining the interaction layer - rather than having the plugin register its components, we would discover those using ScanTypes and use the existing mechanism of declaring assemblies that they are meant for use with "Assets" (see this line).
At the moment there's no standard DI framework used, and the AssemblyRegistry provides a small subset of what DI framework would with the ScanTypes and event callbacks. Introducing an additional DI layer would involve rewriting quite a few bits and would need to be considered thoroughly.
so basically what i find out:
https://docs.microsoft.com/en-us/nuget/reference/nuget-client-sdk
Add locally
also we need cache functionality for loading packages
there is issue about How to manage dependencies for plugins
UPD: So basically. We DONT NEED to reinvent package restoring and adding em. Basically we want:
When project generationg add one more XXX.Editor
project.
XXX.Editor
has ref to XXX
project (core logic)
There can be placed editor plugins (that you can write for project. Example is CustomGridView
that i.e. CustomInspector ).
How to add package:
XXX
). Lets imagine some plugin that affects not only edit mode but provides game logic. some ECS library for example.XXX.Editor
). It contains edit only plugons such as custom grid view. profilers. etc.Engine just need to scan XXX.Editor
for added plugins. We can start without NuGet
impl. Just extra plugins.
So engine build XXX.Editor
and then scan builded assemblies and load em (can be done directly with roslyn runtime.).
Right now we need extension points for editor. Abstractions and some ways to override predefined logic. For example override default play action. Right now it just starts project. If you developing some kinda networking game you wanna start 2 games to test networking. So you can override that behaviour with plugin.
Basically we want some DI analog to reregister some services.
Note: XXX - package name
Upd: even better aproach will be creating of entry point in XXX.Editor and passing there some service to manipulate. And when you want to install plugin you add nuget package and then invoke extension method on that service
Something that came to my head today - I think it would be easier if external plugins are installed globally into your editor and then if the plugin has data to persist in the user's project (i.e. custom asset type) then upon loading the project in another installation of the editor if we detect a type we cannot resolve we would notify the user, giving them a chance to install the plugin - the user's project could have a hint inside it as to where did the plugin come from (because the type that wasn't found could be an unresolved alias without full type name).
The treatment of internal plugins would need to be different because those need reloading and should be clearly referenced in the project as such.
Additionally we should think about providing a hook in the editor where a plugin can check if the user project contains a reference to it's runtime library and if not, add it. I remember there being an issue once that you could add navigation settings into a project that didn't have a reference to Stride.Navigation and it would crash when running - instead on adding the settings we would run the hook to ensure the reference is present.
At the moment there's no standard DI framework used, and the AssemblyRegistry provides a small subset of what DI framework would with the ScanTypes and event callbacks. Introducing an additional DI layer would involve rewriting quite a few bits and would need to be considered thoroughly.
I feel like diagramming the dependency relationships we want to see would be beneficial. The way projects interact in the engine currently creates some traps for circular dependency if we tried to flip things around and provide IoC in a more typical way (see MAUI or WebHost). I've experimented with swapping IServiceRegistry
with IServiceProvider
and introducing the same host builder concepts we see in other .NET applications. It was relatively painless, but I kept encountering areas where the GameSystem
or EntityProcessor
wanted to set up its own dependencies if they were null. Do we need to be more opinionated on that type of thing? Do we require that flexibility moving forward and will it impact your Plugin idea adversely?
On one hand going with full DI would definitely make plenty of things simpler (like switching to the standard logging a abstraction). As you noted one thing is loosing the runtime ability to add services post initialization. A lot of parts of Stride have been implement in a way where things are dynamic and only added when needed, and usually auto detected. So for example when the user wants to add the physics engine (which outside of the editor/assets already is a plugin) all they need to do is add a reference to the library and create a physics component in the scene. Everything else will automatically get wired up. With the change, there would likely be an additional .AddPhysics()
call before creating the Game object. This would likely flow through to how user code is written - e.g. would we keep the attribute based way of defining a processor for a component or switch to a generic factory pattern.
It would definitely be a breaking change and ideally should be a driver for Stride 5.0 - if approached right it could bring a lot of improvements to the design of Stride internals (allowing Game to run headless comes to my mind).
I would suggest creating a new Epic for it and starting to document in it what would be changing and any potential hard parts that needs design discussions.
Two things that came to my head today:
I think the key here is dependency inversion and using a DI framework can help orchestrate it. However, MS's DI framework works well for statically known (or startup known) dependencies, while a plugin system should be robust enough to allow runtime loading and reloading of plugins.
Long time lurker here, not sure if it's been mentioned before, but it feels like the right time in the conversation to mention https://github.com/natemcmaster/DotNetCorePlugins to address concerns about robust loading/reloading of plugins.
@manio143 well. for me it seems much better to configure di before and dont use any attributes at all. Why? Bc you can easilly create factory that will search for class with attribute. But you cant do it backwards.
So precofigurable pipeline looks pretty good.
Reloading in runtime? Do we really need it? What scenario when you want to reload DI at runtime?
I see there only one reason: Stride.Editor. But yeah. there is a way to configure reloadable DI with notifications. ServiceContainer
from MS
So basically why we thing ONLY about c# as plugin language?
We also can use Lua as plugin language for editor (moonsharp)
With dotnet interactive we can have other languages including C# and F#, which in my opinion is better for us.
But i'm not sure how useful it would be for all the rendering related stuff
Following discussion on Discord with @ParadiseFallen
The suggestion is to first focus on enabling user projects to be editor plugins. The Design/Editor project would be loaded by the editor/asset pipeline. This project would not be referenced by the runtime game library. 3rd party code could be copied into the users project to allow for using plugins developed by others. In a later stage 3rd party plugins would be referenced (project/dll/nuget) by the editor project. Only later we would need to understand dependency versioning concerns.
ill take a try to make a basics for this impl. Basically our goals:
MVP is 1-4 points. We need to start there @manio143 ty
Just dropping this here in case it turns out to be relevant for the plugin loading functionality: https://github.com/SteveSandersonMS/DotNetIsolator
I'm gonna update the main description at some point later, after closing down on some more thoughts.
Because the user projects holding editor plugins have currently a few issues with reloading after change, I decided to focus first on NuGet provided plugins. Scenario:
X
which defines a new content type and a component that works with this content.X.Design
which defines the asset type and an asset compiler that allows compiling resources into the content type defined in X
.X
and X.Design
get published on NuGet.orgX
package to their game project.X
won't work without the plugin, it needs to be able to reference it, and we need to load the plugin without having the user take additional actionsI started off with the idea of annotating PackageReference
with a property that can be read by the editor and used to detect packages.
<PackageReference Include="Plugin" Version="1.0.0" IncludeAssets="build;buildTransitive" StridePlugin="true" />
This allows us to load any package as a plugin into editor's memory. However, it doesn't allow for core idea (2) because every plugin has to be explicitly listed in the csproj in the solution for Stride to read it.
NuGet allows packages to define custom PackageType
property (docs). This has an extra benefit of allowing us to search NuGet.org for all packages of that type - https://www.nuget.org/packages?packageType=StridePlugin
On the plugin manifest we would annotate
<PropertyGroup>
<PackageType>StridePlugin</PackageType>
<DevelopmentDependency>true</DevelopmentDependency>
</PropertyGroup>
And user could reference it explicitly pretty much as above
<PackageReference Include="Plugin" Version="1.0.0" IncludeAssets="build;buildTransitive" />
However, we can now potentially traverse the dependency tree and discover plugins referenced by user dependencies without explicitly listing them in their project.
However, currently it's not possible yet. I've opened an issue on NuGet side https://github.com/NuGet/Home/issues/12934 and if it gets implemented it would be very efficient. If not, I have in mind a workaround, such that plugins contain a file with sdplugin
extension in the package and by iterating over all package files we can detect those. The file may be generated with our build targets when PackageType=StridePlugin
is detected.
Another issue with custom package types is https://github.com/NuGet/Home/issues/10468 where VS complains when installing them through UI.
X.Design
likely has a runtime dependency on X
. How would X
be able to take a dependency on X.Design
in order to communicate it has to be loaded by the editor? X.Design
could reference X
with PrivateAssets=all
to skip the reference. However, this then yields to a problem, when X.Design
is referenced explicitly by the user without X
being referenced, as the editor will have no idea that X
needs to be loaded as well.PackageSession.PreLoadPackageDependencies
and PackageSession.UpdateDependencies
for dealing with references.
somewhere else in PackageSession
we would load the plugins into memory.
My idea for solving the circular dependencies didn't work out, but if you're interested https://devblog.dziubiak.pl/stride/01-nuget-plugins/#circular-dependencies Going back to the thinking board...
@manio143 In fact, I don't think this should be any problem at all. In fact, NO project has circular dependencies. You simply won't be able to build it. Accordingly, if you cannot assemble it, then there will be no problem. I suggest moving on
@manio143 In fact, I don't think this should be any problem at all. In fact, NO project has circular dependencies. You simply won't be able to build it. Accordingly, if you cannot assemble it, then there will be no problem. I suggest moving on
I know it's not an issue in this way. But the question remains - if you make a runtime package and a design package which provides the necessary plugin, can we somehow enforce that we add the plugin reference to the users project when they add the runtime reference?
Now, maybe this issue is not an issue at all and the users will be able to figure out that for every runtime package reference if a plugin exists it needs to be added separately. I was trying to see if we can make this experience more seemless. I suppose sometimes the runtime package reference doesn't need to imply using the plugin (code only approach without build time defined assets), so making plugins optional makes sense. It's just the matter of whether users will figure it out, or will they complain that something is missing from the editor even though they are referencing the package.
I think it was a nice idea to look for seamless solutions to that problem, although as a lambda user I wouldn't mind having to add plug-ins to the editor and separately having to add the necessary nuget to the game.
Sometimes a user experience can be simplified with proper error messaging.
@manio143 package manager will solve this issue. you need to have git repo with .stride-plugin-definition file where will be all instrunctions that will understande SPM (stride package manager)
@Doprez As we discussed on Discord, I'm writing up the details to what I was working on for the MVP so that someone else can continue.
To make this simple - we will just try to put in something that after being loaded will log a Warning log. For this assembly to be correctly picked up for asset management we need to put the following in Module.cs
[ModuleInitializer]
public static void Initialize()
{
AssemblyRegistry.Register(typeof(Module).Assembly, AssemblyCommonCategories.Assets);
// and for now for Editor plugins to be discovered also call
AssetsPlugin.RegisterPlugin(typeof(MyPlugin));
}
class MyPlugin : AssetPlugin
{
// override all abstract methods
protected override void SessionLoaded(SessionViewModel session)
{
var logger = GlobalLogger.GetLogger("MyPlugin");
session.AssetLog.AddLogger(logger);
logger.Warning("Session loaded");
}
}
For the asset compiler part of things we can try to get an asset compiler invoked - defined like in CustomAsset.
See comment above for some context. For this MVP we're going to go with the most basic explicit approach.
<PackageReference
Include="Plugin"
Version="1.0.0"
<!-- Don't reference the assembly of the plugin (which may depend on editor libs)
Instead only use build time dependencies -->
IncludeAssets="build;buildTransitive"
<!-- Mark as private so that projects which depend on this one do not pull the plugin
as a dependency themselves -->
PrivateAssets="all"
<!-- Mark as a Stride plugin for the editor code below -->
StridePlugin="true" />
Ok, so now that we've established the how the Developer and User orchestrate their code to define and import a plugin, let's look at how we're going to be discovering it in the editor/asset compiler. First of all let's understand that the plugin loading must be done at the Stride.Core.Assets level, which is the abstract base library for Stride's asset management, shared between editor and asset compiler. The way in which we load the plugins must be independent from the way plugin content is discovered and loaded.
We're going to modify this piece of code in the Dependencies loading logic to discover our plugins
https://github.com/stride3d/stride/blob/cd8e17fdde15b0ef179e7b45eef52bac9a6ccf3d/sources/assets/Stride.Core.Assets/PackageSession.Dependencies.cs#L77-L81
I've spent a lot of time trying to figure out the best/most versatile approach, but for an MVP there's no point being too detailed. We will just check that a PackageReference
has StridePlugin
metadata set to true
and if so, add it to a list of plugins to be loaded. In the future a proper way to handle package version conflicts will be necessary (e.g. someone changes package reference version after launching game studio OR one project in the solution imports plugin with one version and another project imports the same plugin with another version).
class PluginReference(string packageName) {
public Dependency? PackageDependency { get; set; }
}
// add PluginReferences to the SolutionProject
if (packageReference.HasMetadata("StridePlugin") && bool.TryParse(packageReference.GetMetadataValue("StridePlugin"), out var isStridePlugin) && isStridePlugin)
project.PluginReferences.Add(new PluginReference(packageReference.EvaluatedInclude));
Next we need to process the discovered plugins. In the same file there's an UpdateDependencies
method which reads the project.assets.json
lockfile created during package restore phase. We already iterate over the lockfile, so now for any dependency which is on the list of plugins we need to get its DLL location and add it to the project to be loaded later.
void UpdateDependencies(SolutionProject project, bool directDependencies, bool flattenedDependencies,
bool pluginDependencies)
{
//...
if (pluginDependencies) {
foreach (var targetLibrary in projectAssets.Targets.Last().Libraries) {
var reference = project.PluginReferences.FirstOrDefault(pr => pr.packageName == targetLibrary.Name);
if (reference is null) continue;
reference.Dependency = new Dependency(targetLibrary.Name, targetLibrary.Version.ToPackageVersion(), DependencyType.Package);
// Find library path by testing with each PackageFolders
var libraryPath = projectAssets.PackageFolders
.Select(packageFolder => Path.Combine(packageFolder.Path, library.Path.Replace('/', Path.DirectorySeparatorChar)))
.FirstOrDefault(x => Directory.Exists(x));
if (libraryPath != null)
{
// note: because we don't reference the assembly of the plugin - nuget will not populate the DLL path in the restore metadata
// [MVP] find the DLL in any subdirectory
// [Proper] read the nuspec to find the specific path to the DLL to be referenced
// we may need to traverse the dependencies of this package and add their assemblies in here as well - I'd say to leave it for next step after MVP as it becomes a complicated topic
var pathToDll = ...;
reference.Dependency.Assemblies.Add(pathToDll);
}
}
}
}
We now need to actually load those DLLs into the program. I think the correct place to do so is here:
public void LoadMissingReferences(ILogger log, PackageLoadParameters loadParameters = null)
{
LoadMissingDependencies(log, loadParameters);
// TODO: we have to load plugins before we load assets.
LoadMissingAssets(log, Packages.ToList(), loadParameters);
}
See how Package.LoadAssemblies()
works and how it calls AssemblyContainer.LoadAssemblyFromPath()
.
Plugin system RFC
A long await solution to #13. The goal of this document is to provide a description of a plugin system, both in terms of usability and user experience and in terms of steps necessary for its implementation. The discussion underneath should result in materializing a concrete task list which will be implemented over time.
TL;DR
Let's include a
Plugins
property toPackage
definition that defines additional assemblies loaded by GameStudio and AssetCompiler. And a bunch of other things to help write/use plugins. It sounds simple but there's a lot of work behind the scenes to make it happen.Motivation
Stride offers a lot out of the box, however, there's many instances where a user may want to execute custom actions in the GameStudio to help their tasks, have custom assets and custom editors, without necessarily having to recompile Stride from sources. Moreover, allowing plugins would enable Stride users to share their custom code without having to extend the engine in the main repo, or doing painful merges of forks. Also, a plugin oriented architecture should allow for easier development down the line.
Extensibility points
It's important to highlight areas that extensibility will focus around.
Runtime
Runtime libraries, i.e. libraries with components, processors and systems are already in a decent state when it comes to extensibility. Previous work has been done to enable e.g. Stride.Physics, Stride.Video to depend on Stride.Engine and be optional for the user to include. Following their pattern users can create new runtime libraries, inclusion of which will allow their code to be executed. Therefore, this document will not look into further extensibility points to the runtime.
Asset pipeline
The assets related projects (assets compiler and its dependencies) have been designed in a modular way that allows for creating custom assets and custom assets compilers. I've previously shown to be able to create custom assets (here), however, it required my game to take a dependency on
Stride.Core.Assets
, which currently is compiled underStrideEditorTargetFramework
(windows only). The later outlined idea of design projects allows to skip fiddling with the build system and greater cross-platform compatibility.Editor
The Stride GameStudio is the central point for any asset related operations. It has the most potential for extensibility and is also the least familiar to the users in terms of its internals. The following are some things we would want to enable in plugins:
[AssetCompiler]
underThumbnailCompilationContext
)The good news is: there already is an extensibility point for most of those things - StrideAssetsPlugin allows to explicitly register anything that isn't being picked up by attribute or as an interface implementation. A custom plugin just needs a good entry point to be registered (see test for a less than ideal solution).
Usability scenarios
When talking about a plugin system we need to consider how users will interact with it. The following scenarios describe the proposed solution of referencing plugin assemblies.
External plugin - the user includes a reference to a NuGet package in their game project. This package will contain runtime classes. Another package would contain the plugins to allow the user to work with assets later used in their game. A potential way to include the design package would be to extend Package (
sdpkg
file) with aPlugins
property that is a list of{Name: version}
NuGet packages resolved and loaded as plugins.Internal plugin - the user creates a project in their solution that references their game project and
Stride.Assets.Presentation
(or justStride.Core.Assets
), but that project is not referenced by executable projects. The project is also added to thePlugins
list in the game'ssdpkg
file. This project is considered for assembly reloading and any assets defined in it must be fully reloaded (or not - this could be a flag).Considerations
AssemblyRegistry
underAssets
category, or would this be done by the plugin loader? Ideally we would want to block Module initializer execution for safety reasons, but that has been used a lot in Stride own libraries. Now that I think about it, safety goes out the window any time we execute any user code, but at least it would have to be implemented with Stride.Task list
To be added after a discussion.