Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
"husky"
],
"rollForward": false
},
"csharpier": {
"version": "1.2.6",
"commands": [
"csharpier"
],
"rollForward": false
}
}
}
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ updates:
package-ecosystem: github-actions
schedule:
interval: daily
- directory: /
package-ecosystem: gitsubmodule
schedule:
interval: daily
2 changes: 1 addition & 1 deletion .github/workflows/build-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
- name: Get Version
id: version
run: |-
VERSION=$(dotnet build Silksong.DataManager.csproj -getProperty:Version)
VERSION=$(dotnet build src/Silksong.DataManager.csproj -getProperty:Version)
echo Version is $VERSION
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Upload thunderstore artifact
Expand Down
50 changes: 50 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Docs

on:
push:
branches: [main]

permissions:
actions: read
pages: write
id-token: write
Comment thread
BadMagic100 marked this conversation as resolved.

concurrency:
group: pages
cancel-in-progress: false

jobs:
generate-docs:
runs-on: ubuntu-latest

environment:
name: github-pages
url: ${{ steps.deployment.output.page_url }}

steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.x

- name: Build
run: dotnet build -c Release

- name: DocFX build
working-directory: docs
run: dnx docfx --yes
continue-on-error: false

- name: Upload assets
uses: actions/upload-pages-artifact@v4
with:
path: docs/_site

- name: Deploy
id: deployment
uses: actions/deploy-pages@v4
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "docs/silksong-modding-style-docfx"]
path = docs/silksong-modding-style-docfx
url = https://github.com/silksong-modding/silksong-modding-style-docfx
84 changes: 1 addition & 83 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,86 +3,4 @@
DataManager is a mod that saves and loads data - global as well as save slot-specific -
on behalf of other mods.

## Using the mod

To use DataManager as a developer, you will need to do the following:

1. Add a dependency on the Silksong.DataManager NuGet package
2. Add a `BepInDependency` attribute on your plugin class for the `org.silksong-modding.datamanager` plugin
3. Implement one of the data interfaces provided by DataManager (discussed in more depth below)

As a simple example:

```csharp

using Silksong.DataManager;
using BepInEx;

public class SaveData
{
public long rngSeedForSomePurpose;
}

[BepInAutoPlugin(id: "io.github.username.my-mod")]
[BepInDependency("org.silksong-modding.datamanager")]
public partial class MyPlugin : BaseUnityPlugin, ISaveDataMod<SaveData>
{
public SaveData? SaveData { get; set; }

// rest of mod class
}
```

## Types of data

DataManager supports global data, which is an alternative to BepInEx configuration, and per-save data
which is effectively a modded extension to save data.

All data types are handled by generic interfaces, such as `IGlobalDataMod<T>` where T is the type of data you want to
store. DataManager will automatically serialize your data as JSON at appropriate times. DataManager uses the
Newtonsoft.Json library for serialization and the serialization behavior can be altered by using Newtonsoft.Json
attributes in advanced use cases.

In the case that a mod needs to add its own data manually instead of or in addition to these interfaces, the paths
used by DataManager can all be found in the `DataPaths` class.

### Global data

Global data is available for cases where data is needed across multiple saves, or outside of saves such as in the menu.
DataManager offers 2 types of global data which mod developers can choose between based on their use case. Save data
is automatically sychronized across devices by Steam Cloud.

#### `IProfileDataMod`

`IProfileDataMod<T>` is ideal for mods that want a more powerful alternative to BepInEx configuration. Data is stored
in the same location as BepInEx configuration files, which means that is scoped to your profile in the mod loader. It
is loaded when the game opens and saved when the game closes; you can access it in your plugin's `Start` method.

#### `IGlobalDataMod`

`IGlobalDataMod<T>` is an alternative to `IProfileDataMod<T>` which is saved in the save data directory, in
`<save directory>/Modded/Global/<plugin ID>.json.dat`. There are 2 benefits of this location:
1. It will be synced across devices by Steam Cloud
2. It will be available across multiple profiles

Therefore, if either of these use cases are relevant to your mod, you should use `IGlobalDataMod<T>`. Similar to
`IProfileDataMod<T>`, this data is loaded when the game opens and saved when the game closes; you can access it in
your plugin's `Start` method.

### Save-specific data

Save-specific data is a way for your mod to add its own data to a save file. DataManager supports both mutable
and immutable data.

#### `IOnceSaveDataMod`

`IOnceSaveDataMod<T>` provides immutable data which is set at the beginning of the file and never updated again.
It is stored in `<save directory>/Modded/userN/OncePerSave/<mod ID>.json.dat`. Data is saved when a game starts,
specifically at the end of `GameManager.StartNewGame` and loaded when loading into a file. Immutable data results
in fewer disk operations which makes the game more performant, so developers are encouraged to use it when possible.

#### `ISaveDataMod`

`ISaveDataMod<T>` is used for all mutable save data. It is stored in
`<save directory>/Modded/userN/SaveData/<mod ID>.json.dat`. Data is saved when the game is saved and loaded when
loading into a file.
For documentation and examples, see the [documentation](https://docs.silksong-modding.org/Silksong.DataManager).
2 changes: 1 addition & 1 deletion Silksong.DataManager.slnx
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<Solution>
<Project Path="Silksong.DataManager.csproj" />
<Project Path="src/Silksong.DataManager.csproj" />
</Solution>
8 changes: 8 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**/DROP/
/**/TEMP/
/**/packages/
/**/bin/
/**/obj/
_site
/api/*.yml
/api/.manifest
52 changes: 52 additions & 0 deletions docs/articles/datatypes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Data Types

DataManager supports global data, which is an alternative to BepInEx configuration, and per-save data
which is effectively a modded extension to save data.

All data types are handled by generic interfaces, such as `IGlobalDataMod<T>` where T is the type of data you want to
store. DataManager will automatically serialize your data as JSON at appropriate times. DataManager uses the
Newtonsoft.Json library for serialization and the serialization behavior can be altered by using Newtonsoft.Json
attributes in advanced use cases.

In the case that a mod needs to add its own data manually instead of or in addition to these interfaces, the paths
used by DataManager can all be found in the `DataPaths` class.

## Global data

Global data is available for cases where data is needed across multiple saves, or outside of saves such as in the menu.
DataManager offers 2 types of global data which mod developers can choose between based on their use case.

### `IProfileDataMod`

`IProfileDataMod<T>` is ideal for mods that want a more powerful alternative to BepInEx configuration. Data is stored
in the same location as BepInEx configuration files, which means that is scoped to your profile in the mod loader. It
is loaded when the game opens and saved when the game closes; you can access it in your plugin's `Start` method.

### `IGlobalDataMod`

`IGlobalDataMod<T>` is an alternative to `IProfileDataMod<T>` which is saved in the save data directory, in
`<save directory>/Modded/Global/<plugin ID>.json.dat`. There are 2 benefits of this location:
1. It will be synced across devices by Steam Cloud
2. It will be available across multiple profiles

Therefore, if either of these use cases are relevant to your mod, you should use `IGlobalDataMod<T>`. Similar to
`IProfileDataMod<T>`, this data is loaded when the game opens and saved when the game closes; you can access it in
your plugin's `Start` method.

## Save-specific data

Save-specific data is a way for your mod to add its own data to a save file. DataManager supports both mutable
and immutable data.

### `IOnceSaveDataMod`

`IOnceSaveDataMod<T>` provides immutable data which is set at the beginning of the file and never updated again.
It is stored in `<save directory>/Modded/userN/OncePerSave/<mod ID>.json.dat`. Data is saved when a game starts,
specifically at the end of `GameManager.StartNewGame` and loaded when loading into a file. Immutable data results
in fewer disk operations which makes the game more performant, so developers are encouraged to use it when possible.

### `ISaveDataMod`

`ISaveDataMod<T>` is used for all mutable save data. It is stored in
`<save directory>/Modded/userN/SaveData/<mod ID>.json.dat`. Data is saved when the game is saved and loaded when
loading into a file.
40 changes: 40 additions & 0 deletions docs/articles/examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
uid: ExamplesArticle
---

# Examples

The following is the standard pattern for mods implementing ISaveDataMod.

```csharp
using System.Diagnostics.CodeAnalysis;
using Silksong.DataManager;
using BepInEx;

// The Save Data class can be any serializable type
public class SaveData
{
public long TheSavedIntValue { get; set; }
}

[BepInAutoPlugin(id: "io.github.username.my-mod")]
[BepInDependency(Silksong.DataManager.DataManagerPlugin.Id)]
public partial class MyPlugin : BaseUnityPlugin, ISaveDataMod<SaveData>
{
private SaveData _saveData = new SaveData();

[AllowNull]
Comment thread
flibber-hk marked this conversation as resolved.
public SaveData SaveData
{
get => _saveData;
set => _saveData = value ?? new SaveData();
}

// rest of mod class
}
```

You can freely access members of the SaveData object; when the user enters an existing save file,
DataManager will set this to the value from the last time they used that save file,
and when the user returns to menu, it will be replaced with a new object.

36 changes: 36 additions & 0 deletions docs/articles/lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Lifecycle

In this article, we give the times at which DataManager operations run.
Comment thread
BadMagic100 marked this conversation as resolved.

## Global data

* DataManager loads Global and Profile data during the Plugin.Start method.
It is likely that this will run before your plugin's Start method, but this is
not guaranteed by Unity.
If you need data this early, you may want to wait a frame before accessing the data,
although this should not be necessary for most pluggins.
* DataManager writes Global and Profile data to disk when the user quits the game.
Note - this will not happen if the game crashed.

## Save data

* When the user creates a new save file, DataManager will not do anything to SaveData mods.
It is your responsibility to construct an instance of your save data class, although this will be done
automatically if you use the example code (see <xref:ExamplesArticle>).
* When the user enters an existing save file, DataManager will set the SaveData
property to the save data from the previous time they entered the file. If there
was no save data then (e.g. if the user installed your mod after they created the save),
then DataManager will not do anything at this moment.
* Whenever the game saves, DataManager will get the SaveData property and write it to disk.
This will happen regardless of if your save data was loaded when the user entered the file.
* When the user quits to menu, DataManager will set the SaveData property to null
(after the save data gets saved to disk).
If you are using the code in the example, this will be intercepted
and a new SaveData object will be constructed (for if they enter a new file).
* When the user deletes a save file, all data managed by DataManager for that save file
will be deleted too.

## OnceSaveData

* When the user creates a new save file, the OnceSaveData will be retrieved and written to disk.
* Whenever the SaveData is loaded for a file (or set to null), the OnceSaveData will be as well.
5 changes: 5 additions & 0 deletions docs/articles/toc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- name: Getting Started
- href: ../index.md
- href: datatypes.md
- href: examples.md
- href: lifecycle.md
48 changes: 48 additions & 0 deletions docs/docfx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json",
"metadata": [
{
"src": [
{
"src": "../src",
"files": [
"**/*.csproj"
]
}
],
"dest": "api",
"filter": "filterconfig.yml"
}
],
"build": {
"content": [
{
"files": [
"**/*.{md,yml}"
],
"exclude": [
"_site/**"
]
}
],
"resource": [
{
"files": [
"images/**"
]
}
],
"output": "_site",
"template": [
"default",
"modern",
"silksong-modding-style-docfx"
],
"globalMetadata": {
"_appName": "DataManager",
"_appTitle": "DataManager",
"_enableSearch": true,
"pdf": false
}
}
}
4 changes: 4 additions & 0 deletions docs/filterconfig.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### YamlMime:ManagedReference
apiRules:
- exclude:
uidRegex: 'Silksong\.DataManager\.DataManagerPlugin'
Loading