Skip to content

Commit 3c7717b

Browse files
committed
Add StarMap section to mod.toml and improve API documentation
1 parent fb6680c commit 3c7717b

File tree

11 files changed

+484
-244
lines changed

11 files changed

+484
-244
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ It makes use of Assembly Load Contexts to ensure mod dependencies are managed se
1515

1616
## Mod location
1717

18-
Mods should be installed in the mods folder in the KSA installation, any KSA mod that has a dll with the same name as the mod that impelements the IStarMapMod interface will be loaded.
18+
Mods should be installed in the contents folder in the KSA installation, StarMap makes use of the
1919

2020
## Mod creation
2121

StarMap.API/README.md

Lines changed: 202 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,209 @@
11
# StarMap API
22

33
This package provides the API for mods to interface with the [StarMap](https://github.com/StarMapLoader/StarMap) modloader.
4-
The main class of the mod should be marked by the StarMapMod attribute.
5-
Then methods within this class can be marked with any of the StarMapMethod attributes.
6-
At the initialization of the mod within KSA, an instance of the StarMapMod is created, only the first class that has this attribute will be considered.
7-
Any method within this class that has any of the attributes will used, so if two methods use StarMapImmediateLoad, both will be called.
84

9-
## Attributes
5+
## How to create mods
106

11-
- StarMapMod: Main attribute to mark the mod class.
12-
- StarMapImmediateLoad: Called immediatly when the mod is loaded in KSA.
13-
- StarMapAllModsLoaded: Called once all mods are loaded, can be used when this mod has a dependency on another mod.
14-
- StarMapUnload: Called when KSA is unloaded.
15-
- StarMapBeforeGui: Called just before KSA starts drawing its Ui.
16-
- StarMapAfterGui: Called after KSA has drawn its Ui.
7+
### General architecture
178

18-
## Dependencies
9+
A StarMap mod is in essence an extension of KSA mods.
10+
Every mod will at minimum contain a mod.toml (which is also the case for KSA mods) as well as an entry assembly.
1911

20-
Mods can define what mods they depend on, and what assemblies they want to import from that mod.
21-
They can as well define what assemblies they want exported to other mods.
12+
#### mod.toml
13+
14+
While it is not stricly neccesary, it is adviced to add StarMap info to the mod.toml, at its most basic, a mod.toml should like like this:
15+
16+
```toml
17+
name = "StarMap.SimpleMod"
18+
19+
[StarMap]
20+
EntryAssembly = "StarMap.SimpleMod"
21+
```
22+
23+
The name will be the modid of your mod, this should be the same name as the folder in which this lives in the Content folder.
24+
The "StarMap" section is optional, if it is provided, at mimimum, it should provide the name of the assembly which StarMap will initially load (without the dll extension).
25+
If it is not provided, StarMap will search for a [modid].dll in the mod folder.
26+
27+
#### Entry assembly
28+
29+
The above mentioned entry assembly will be the first thing that StarMap will load.
30+
Within this assembly, StarMap will search for a class that has the [StarMapMod](#starmapmod) attribute. It will use the first that it finds.
31+
This class will be the core part of the mod, any entry from StarMap will be done through this class.
32+
To add functionality to the mod, methods with attributes can be added, which attributes are available and what their functionality is, can be found in the [Attributes API reference](#attributes).
33+
These methods will be called as instance methods on the mod class, throughout the runtime of KSA, StarMap will create once instance of this class and reuse it.
34+
The implemenation should follow the signatures shown in the example code.
35+
36+
### Dependencies
37+
38+
In many cases, mods will have dependencies on other mods, or will want to overwrite functionality of other mods.
39+
To achieve this, some extra configuration is required, this configuration is confined to the mod.toml within the StarMap section.
40+
41+
#### Exported assemblies
42+
43+
First of all, mods can configure what assemblies should be exposed to other mods, by default all assemblies that are provided with the mod can be accessed from other mods, this can be changed with the ExportedAssemblies.
44+
In below example, only the StarMap.SimpleMod.Dependency(.dll) assembly will be accessable from other mods (more info in [imported and exported assemblies](#imported-and-exported-assemblies)).
45+
46+
```toml
47+
name = "StarMap.SimpleMod2"
48+
49+
[StarMap]
50+
EntryAssembly = "StarMap.SimpleMod2"
51+
ExportedAssemblies = [
52+
"StarMap.SimpleMod.Dependency"
53+
]
54+
```
55+
56+
#### Mod dependency
57+
58+
Then, mods can define what mods they want to define on, they can do this by adding a new ModDependencies list entry in the mod.toml
59+
60+
```toml
61+
name = "StarMap.SimpleMod"
62+
63+
[StarMap]
64+
EntryAssembly = "StarMap.SimpleMod"
65+
66+
[[StarMap.ModDependencies]]
67+
ModId = "StarMap.SimpleMod2"
68+
Optional = false
69+
ImportedAssemblies = [
70+
"StarMap.SimpleMod.Dependency"
71+
]
72+
```
73+
74+
In above example, it is provided that StarMap.SimpleMod wants to depend on StarMap.SimpleMod2, this dependency is not optional and the mod wants to access the StarMap.SimpleMod.Dependency assembly.
75+
Following fields can be used
76+
77+
- The ModId should be the same as is provided as the name field in the mod.toml of the dependency mod.
78+
- The optional field (default false) defines if this dependency is optional, more info in the [loading strategy](#dependency-loading-strategy)
79+
- The ImportedAssemblies field contains a list of assemblies that this mod intends to use from the dependency (more info in [imported and exported assemblies](#imported-and-exported-assemblies)).
80+
81+
#### Imported and exported assemblies
82+
83+
The goal of the imported and exported assembly fields is to compile a list of assemblies that will be provided to a mod from a dependency, below is the behaviour depending on the content of both fields:
84+
85+
- If both fields are not filled in, the list will contain the entry assembly of the dependency.
86+
- If only 1 of the lists is filled in, it will use this list to provide the assemblies.
87+
- If both lists are defined, the intersect of the two will be used.
88+
89+
## Mod loading strategy
90+
91+
When StarMap is started, it will start with loading the manifest.toml (in the same way KSA does it, only sooner), it will then start loading mods from top to bottom.
92+
It will first load the mod.toml, if the file does not exists, mod loading will not work, if there is no StarMap section, it will use a default configuration.
93+
Using the config, if there are dependencies, it will first check if these dependencies are already loaded. If they are all loaded, mod loading continues,
94+
otherwise, it stores what dependencies are still needed and continues to the next mod.
95+
It will then search for the entry assembly, if it does not exists, loading will be stopped, otherwise, it will load the assembly. Then will search for a class with the StarMapMod attribute and create an instance. With the instance, it goes over the known attributes and stores a reference to the methods, allowing for quick quering, it stores the StarMapBeforeMain and StarMapImmediateLoad methods seperatly because they are mod specific.
96+
Once the mod is fully loaded, it will call the StarMapBeforeMain method, if there is any.
97+
98+
Now that the mod has been loaded, it checks the list of mods that are waiting for dependencies, and if there are any that are waiting for this mod. If so, it removes itself from the waiting dependencies and checks if the mod can now be loaded, if so, the mod is loaded and the StarMapBeforeMain of that mod is called.
99+
100+
It does this for all the mods in the manifest.
101+
Once it has tried loading all the mods, it gets the mods that are stil waiting and checks them again.
102+
If for a waiting mod, all its dependencies are optional, it will now load this mod. The implementation of the mod should ensure it can handle the optional dependency can be absent.
103+
It keeps looping over the list of waiting mods until it has gone through the list once without being able to load a new mod, this indicates there are no more mods that can load with the provided mods, and gives up on loading these mods.
104+
105+
Now StarMap will start KSA, which in turn will call StarMapImmediateLoad for each mod, if implemented.
106+
107+
## Examples
108+
109+
Some examples can be found in the [example mods repository](https://github.com/StarMapLoader/StarMap-ExampleMods)
110+
111+
## API reference
112+
113+
### Attributes
114+
115+
#### StarMapMod
116+
117+
Namespace: `StarMap.API`
118+
Assembly: `StarMap.API`
119+
Target: Class
120+
121+
Marks the main class for a StarMap mod.
122+
Only attributes on methods within classes marked with this attribute will be considered.
123+
124+
```csharp
125+
[StarMapMod]
126+
public class ModClass
127+
```
128+
129+
#### StarMapBeforeMain
130+
131+
Namespace: `StarMap.API`
132+
Assembly: `StarMap.API`
133+
Target: Method
134+
135+
Methods marked with this attribute will be called before KSA is started.
136+
137+
```csharp
138+
[StarMapBeforeMain]
139+
public void ModMethod()
140+
```
141+
142+
#### StarMapImmediateLoad
143+
144+
Namespace: `StarMap.API`
145+
Assembly: `StarMap.API`
146+
Target: Method
147+
148+
Methods marked with this attribute will be called immediately when the mod is loaded by KSA.
149+
It is called before the `KSA.Mod.PrepareSystems` method for each mod
150+
151+
```csharp
152+
[StarMapBeforeMain]
153+
public void ModMethod(KSA.Mod mod)
154+
```
155+
156+
#### StarMapAllModsLoaded
157+
158+
Namespace: `StarMap.API`
159+
Assembly: `StarMap.API`
160+
Target: Method
161+
162+
Methods marked with this attribute will be called when all mods are loaded.
163+
It is called after the `KSA.ModLibrary.LoadAll` method.
164+
165+
```csharp
166+
[StarMapAllModsLoaded]
167+
public void ModMethod()
168+
```
169+
170+
#### StarMapUnload
171+
172+
Namespace: `StarMap.API`
173+
Assembly: `StarMap.API`
174+
Target: Method
175+
176+
Methods marked with this attribute will be called when KSA is unloaded
177+
178+
```csharp
179+
[StarMapUnload]
180+
public void ModMethod()
181+
```
182+
183+
#### StarMapBeforeGui
184+
185+
Namespace: `StarMap.API`
186+
Assembly: `StarMap.API`
187+
Target: Method
188+
189+
Methods marked with this attribute will be called before KSA starts creating its ImGui interface.
190+
It is called just before the `KSA.Program.OnDrawUi` method.
191+
192+
```csharp
193+
[StarMapBeforeGui]
194+
public void ModMethod(double dt)
195+
```
196+
197+
#### StarMapAfterGui
198+
199+
Namespace: `StarMap.API`
200+
Assembly: `StarMap.API`
201+
Target: Method
202+
203+
Methods marked with this attribute will be called when KSA has finished creating its ImGui interface.
204+
It is called just after the `KSA.Program.OnDrawUi` method.
205+
206+
```csharp
207+
[StarMapAfterGui]
208+
public void ModMethod(double dt)
209+
```

StarMap.API/StarMap.API.csproj

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<PropertyGroup>
4-
<TargetFramework>net10.0</TargetFramework>
5-
<ImplicitUsings>enable</ImplicitUsings>
6-
<Nullable>enable</Nullable>
7-
</PropertyGroup>
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
8+
<NoWarn>1591</NoWarn>
9+
<PackageReadmeFile>README.md</PackageReadmeFile>
10+
</PropertyGroup>
811

9-
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
10-
<Reference Include="..\..\Import\KSA.dll" />
11-
</ItemGroup>
12+
<ItemGroup>
13+
<None Include="README.md" Pack="true" PackagePath="\" />
14+
</ItemGroup>
1215

13-
<ItemGroup Condition="'$(Configuration)' != 'Debug'">
14-
<PackageReference Include="StarMap.KSA.Dummy" Version="1.0.11">
15-
<IncludeAssets>compile; build; analyzers</IncludeAssets>
16-
<PrivateAssets>all</PrivateAssets>
17-
</PackageReference>
18-
</ItemGroup>
19-
</Project>
16+
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
17+
<Reference Include="..\..\Import\KSA.dll" />
18+
</ItemGroup>
19+
20+
<ItemGroup Condition="'$(Configuration)' != 'Debug'">
21+
<PackageReference Include="StarMap.KSA.Dummy" Version="1.0.11">
22+
<IncludeAssets>compile; build; analyzers</IncludeAssets>
23+
<PrivateAssets>all</PrivateAssets>
24+
</PackageReference>
25+
</ItemGroup>
26+
</Project>

StarMap.Core/ModRepository/ModAssemblyLoadContext.cs

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ internal class ModAssemblyLoadContext : AssemblyLoadContext
99
private readonly AssemblyLoadContext _coreAssemblyLoadContext;
1010
private readonly AssemblyDependencyResolver _modDependencyResolver;
1111

12-
public ModInformation? ModInfo { get; set; }
12+
public RuntimeMod? RuntimeMod { get; set; }
1313

14-
public ModAssemblyLoadContext(string modId, string modDirectory, AssemblyLoadContext coreAssemblyContext)
14+
public ModAssemblyLoadContext(string modId, string entryAssemblyLocation, AssemblyLoadContext coreAssemblyContext)
1515
: base()
1616
{
1717
_coreAssemblyLoadContext = coreAssemblyContext;
1818

1919
_modDependencyResolver = new AssemblyDependencyResolver(
20-
Path.GetFullPath(Path.Combine(modDirectory, modId + ".dll"))
20+
Path.GetFullPath(entryAssemblyLocation)
2121
);
2222
}
2323

@@ -46,36 +46,15 @@ public ModAssemblyLoadContext(string modId, string modDirectory, AssemblyLoadCon
4646
}
4747
}
4848

49-
if (ModInfo is ModInformation modInfo && modInfo.Dependencies.Count > 0)
49+
if (RuntimeMod is RuntimeMod modInfo && modInfo.Dependencies.Count > 0)
5050
{
51-
foreach (var (dependencyInfo, importedAssemblies) in modInfo.Dependencies)
51+
foreach (var (dependency, importedAssemblies) in modInfo.Dependencies)
5252
{
53-
bool ShouldTryLoad()
54-
{
55-
var hasExportedAssemblies = dependencyInfo.ExportedAssemblies.Count > 0;
56-
var hasImportedAssemblies = importedAssemblies.Count > 0;
57-
58-
if (!hasImportedAssemblies && !hasExportedAssemblies) {
59-
if (dependencyInfo.Config.EntryAssembly == assemblyName.Name)
60-
return true;
61-
62-
return false;
63-
}
64-
65-
if (hasExportedAssemblies && !dependencyInfo.ExportedAssemblies.Contains(assemblyName.Name ?? string.Empty))
66-
return false;
67-
68-
if (hasImportedAssemblies && !importedAssemblies.Contains(assemblyName.Name ?? string.Empty))
69-
return false;
70-
71-
return true;
72-
}
73-
74-
if (ShouldTryLoad())
53+
if (importedAssemblies.Contains(assemblyName.Name ?? string.Empty))
7554
{
7655
try
7756
{
78-
var asm = dependencyInfo.ModAssemblyLoadContext.LoadFromAssemblyName(assemblyName);
57+
var asm = dependency.ModAssemblyLoadContext.LoadFromAssemblyName(assemblyName);
7958
if (asm != null)
8059
return asm;
8160
}

0 commit comments

Comments
 (0)