Skip to content

Commit

Permalink
Update CSync usage (v2 released) (#104)
Browse files Browse the repository at this point in the history
* update CSync usage (v2.0.0)

* fix dead link
  • Loading branch information
Owen3H authored Mar 1, 2024
1 parent edd5774 commit 698bcf3
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 107 deletions.
11 changes: 10 additions & 1 deletion docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,16 @@ export default defineConfig({
collapsed: true,
items: [
{ text: 'Usage Guide', link: '/dev/apis/csync/usage-guide' },
{ text: 'Troubleshooting', link: '/dev/apis/csync/troubleshooting' }
{ text: 'Migrating to v2', link: '/dev/apis/csync/v2-migration' },
{
text: 'Outdated',
link: '/dev/apis/csync/outdated',
collapsed: true,
items: [
{ text: 'Usage Guide', link: '/dev/apis/csync/outdated/usage-guide-outdated' },
{ text: 'Troubleshooting', link: '/dev/apis/csync/outdated/troubleshooting' }
]
}
]
},
]
Expand Down
5 changes: 3 additions & 2 deletions docs/dev/apis/csync.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
prev: false
next: false
next: true
description: A guide to synchronizing a BepInEx config file between host and clients using the CSync library.
---

Expand Down Expand Up @@ -30,4 +30,5 @@ Alternatively, you can install it visually via the **NuGet** package manager.

## Overview
- [Usage Guide](/dev/apis/csync/usage-guide) - The guide/tutorial to using this library.
- [Troubleshooting](/dev/apis/csync/troubleshooting) - Common issues and their solutions.
- [Migrating to v2](/dev/apis/csync/v2-migration) - How to update from v1 to v2.
- [Troubleshooting](/dev/apis/csync/outdated/troubleshooting) - Common issues and their solutions. (Pre v2)
9 changes: 9 additions & 0 deletions docs/dev/apis/csync/outdated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
prev: false
next: true
description: The outdated (pre v2) guide to using CSync.
---

# Outdated Guide (Pre v2.0.0)
The following usage guide and troubleshooting sections are outdated.<br>
If you are a mod dev, consider migrating to v2 using [this](/dev/apis/csync/v2-migration) page.
File renamed without changes.
162 changes: 162 additions & 0 deletions docs/dev/apis/csync/outdated/usage-guide-outdated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
prev: false
next: true
description: The main guide to using CSync.
---

# Guide to using CSync

## 1. Creating a serializable config class
To begin, we will create a new class that will inherit from `SyncedInstance`.<br>
We must then add the `[DataContract]` attribute for this to be synced with clients.

```cs
[DataContract]
public class Config : SyncedInstance<Config>
```

Within this class, we can now begin writing out our config entries that we want to sync.<br>
We must also mark them with the `[DataMember]` attribute for the serializer to recognize them.

```cs
[DataContract]
public class Config : SyncedInstance<Config> {
public ConfigEntry<float> DISPLAY_DEBUG_INFO { get; private set; }

[DataMember] public SyncedEntry<float> MOVEMENT_SPEED { get; private set; }
[DataMember] public SyncedEntry<float> CLIMB_SPEED { get; private set; }
}
```

::: warning
When using client side and synced entries in the same class, any instance of `ConfigEntry` should **NOT** be marked with `[DataMember]` to avoid BepInEx runtime errors.
:::

## 2. Binding config entries
Before binding, we will add the following line at the top of the constructor.
```cs
InitInstance(this);
```

We can now bind our entries to the **BepInEx** config file like usual, however we will use the dedicated `BindSyncedEntry` extension method provided by **CSync**.

```cs
public Config(ConfigFile cfg) {
InitInstance(this);

MOVEMENT_SPEED = cfg.BindSyncedEntry("Movement", "fMovementSpeed", 4.1f,
"The base speed at which the player moves. This is NOT a multiplier."
);

CLIMB_SPEED = cfg.BindSyncedEntry("Movement", "fClimbSpeed", 3.9f,
"The base speed at which the player climbs. This is NOT a multiplier."
);
}
```

## 3. Adding synchronization methods
We will now place the following methods within the class, making sure to replace `PluginInfo.PLUGIN_GUID` if it is defined elsewhere.

::: warning
If you specify a GUID that is not unique, your mod will conflict with other mods that use **CSync**.
:::

```cs
internal static void RequestSync() {
if (!IsClient) return;

using FastBufferWriter stream = new(IntSize, Allocator.Temp);

// Method `OnRequestSync` will then get called on host.
stream.SendMessage($"{PluginInfo.PLUGIN_GUID}_OnRequestConfigSync");
}

internal static void OnRequestSync(ulong clientId, FastBufferReader _) {
if (!IsHost) return;

byte[] array = SerializeToBytes(Instance);
int value = array.Length;

using FastBufferWriter stream = new(value + IntSize, Allocator.Temp);

try {
stream.WriteValueSafe(in value, default);
stream.WriteBytesSafe(array);

stream.SendMessage($"{PluginInfo.PLUGIN_GUID}_OnReceiveConfigSync", clientId);
} catch(Exception e) {
Plugin.Logger.LogError($"Error occurred syncing config with client: {clientId}\n{e}");
}
}

internal static void OnReceiveSync(ulong _, FastBufferReader reader) {
if (!reader.TryBeginRead(IntSize)) {
Plugin.Logger.LogError("Config sync error: Could not begin reading buffer.");
return;
}

reader.ReadValueSafe(out int val, default);
if (!reader.TryBeginRead(val)) {
Plugin.Logger.LogError("Config sync error: Host could not sync.");
return;
}

byte[] data = new byte[val];
reader.ReadBytesSafe(ref data, val);

try {
SyncInstance(data);
} catch(Exception e) {
Plugin.Logger.LogError($"Error syncing config instance!\n{e}");
}
}
```

## 4. Apply Harmony patches
Add in the following method and make sure the GUID is defined just like the previous step.

```cs
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerControllerB), "ConnectClientToPlayerObject")]
public static void InitializeLocalPlayer() {
if (IsHost) {
MessageManager.RegisterNamedMessageHandler($"{PluginInfo.PLUGIN_GUID}_OnRequestConfigSync", OnRequestSync);
Synced = true;

return;
}

Synced = false;
MessageManager.RegisterNamedMessageHandler($"{PluginInfo.PLUGIN_GUID}_OnReceiveConfigSync", OnReceiveSync);
RequestSync();
}
```

Finally, we need to make sure the client reverts back to their own config upon leaving.

```cs
[HarmonyPostfix]
[HarmonyPatch(typeof(GameNetworkManager), "StartDisconnect")]
public static void PlayerLeave() {
Config.RevertSync();
}
```

## Finalizing and Publishing
It is recommended you inform **BepInEx** that you depend upon **CSync**.<br>
You can do this by adding a `BepInDependency` attribute and specifying the GUID of this library.

```cs
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
[BepInDependency("io.github.CSync")] // [!code ++]
public class MyPlugin : BaseUnityPlugin
```

If you plan to upload your mod to **Thunderstore**, make sure you also specify the dependency within your `manifest.json` file by adding its **Thunderstore** ID to the array.
```json
"dependencies": ["BepInEx-BepInExPack-5.4.2100", "Owen3H-CSync-1.0.8"]
```

::: info NOTE
Please ensure your manifest contains the latest version, the one seen above may be outdated!
:::
123 changes: 19 additions & 104 deletions docs/dev/apis/csync/usage-guide.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,51 @@
---
prev: false
next: true
next: false
description: The main guide to using CSync.
---

# Guide to using CSync

## 1. Creating a serializable config class
To begin, we will create a new class that will inherit from `SyncedInstance`.<br>
To begin, we will create a new class that will inherit from `SyncedConfig`.<br>
We must then add the `[DataContract]` attribute for this to be synced with clients.

```cs
[DataContract]
public class Config : SyncedInstance<Config>
public class ExampleConfig : SyncedConfig<ExampleConfig>
```

Within this class, we can now begin writing out our config entries that we want to sync.<br>
Within this class, we can now begin writing out our config entries that we want to sync using `SyncedEntry`.
We must also mark them with the `[DataMember]` attribute for the serializer to recognize them.

```cs
[DataContract]
public class Config : SyncedInstance<Config> {
public class ExampleConfig : SyncedConfig<ExampleConfig> {
public ConfigEntry<float> DISPLAY_DEBUG_INFO { get; private set; }

[DataMember] public SyncedEntry<float> MOVEMENT_SPEED { get; private set; }
[DataMember] public SyncedEntry<float> CLIMB_SPEED { get; private set; }
[DataMember] public SyncedEntry<float> MOVEMENT_SPEED { get; private set; } // [!code ++]
[DataMember] public SyncedEntry<float> CLIMB_SPEED { get; private set; } // [!code ++]
}
```

::: warning
When using client side and synced entries in the same class, any instance of `ConfigEntry` should **NOT** be marked with `[DataMember]` to avoid BepInEx runtime errors.
:::

## 2. Binding config entries
Before binding, we will add the following line at the top of the constructor.
Before binding, we must do a few things.<br>
To begin, make sure your config constructor implements `base()` with the GUID of your mod.

```cs
public ExampleConfig(ConfigFile cfg) : base("MyModName")
```

Then, add the following line at the top of this constructor.
```cs
InitInstance(this);
ConfigManager.Register(this);
```

We can now bind our entries to the **BepInEx** config file like usual, however we will use the dedicated `BindSyncedEntry` extension method provided by **CSync**.

```cs
public Config(ConfigFile cfg) {
InitInstance(this);
public ExampleConfig(ConfigFile cfg) : base("MyModName") {
ConfigManager.Register(this);

MOVEMENT_SPEED = cfg.BindSyncedEntry("Movement", "fMovementSpeed", 4.1f,
"The base speed at which the player moves. This is NOT a multiplier."
Expand All @@ -54,94 +57,6 @@ public Config(ConfigFile cfg) {
}
```

## 3. Adding synchronization methods
We will now place the following methods within the class, making sure to replace `PluginInfo.PLUGIN_GUID` if it is defined elsewhere.

::: warning
If you specify a GUID that is not unique, your mod will conflict with other mods that use **CSync**.
:::

```cs
internal static void RequestSync() {
if (!IsClient) return;

using FastBufferWriter stream = new(IntSize, Allocator.Temp);

// Method `OnRequestSync` will then get called on host.
stream.SendMessage($"{PluginInfo.PLUGIN_GUID}_OnRequestConfigSync");
}

internal static void OnRequestSync(ulong clientId, FastBufferReader _) {
if (!IsHost) return;

byte[] array = SerializeToBytes(Instance);
int value = array.Length;

using FastBufferWriter stream = new(value + IntSize, Allocator.Temp);

try {
stream.WriteValueSafe(in value, default);
stream.WriteBytesSafe(array);

stream.SendMessage($"{PluginInfo.PLUGIN_GUID}_OnReceiveConfigSync", clientId);
} catch(Exception e) {
Plugin.Logger.LogError($"Error occurred syncing config with client: {clientId}\n{e}");
}
}

internal static void OnReceiveSync(ulong _, FastBufferReader reader) {
if (!reader.TryBeginRead(IntSize)) {
Plugin.Logger.LogError("Config sync error: Could not begin reading buffer.");
return;
}

reader.ReadValueSafe(out int val, default);
if (!reader.TryBeginRead(val)) {
Plugin.Logger.LogError("Config sync error: Host could not sync.");
return;
}

byte[] data = new byte[val];
reader.ReadBytesSafe(ref data, val);

try {
SyncInstance(data);
} catch(Exception e) {
Plugin.Logger.LogError($"Error syncing config instance!\n{e}");
}
}
```

## 4. Apply Harmony patches
Add in the following method and make sure the GUID is defined just like the previous step.

```cs
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerControllerB), "ConnectClientToPlayerObject")]
public static void InitializeLocalPlayer() {
if (IsHost) {
MessageManager.RegisterNamedMessageHandler($"{PluginInfo.PLUGIN_GUID}_OnRequestConfigSync", OnRequestSync);
Synced = true;

return;
}

Synced = false;
MessageManager.RegisterNamedMessageHandler($"{PluginInfo.PLUGIN_GUID}_OnReceiveConfigSync", OnReceiveSync);
RequestSync();
}
```

Finally, we need to make sure the client reverts back to their own config upon leaving.

```cs
[HarmonyPostfix]
[HarmonyPatch(typeof(GameNetworkManager), "StartDisconnect")]
public static void PlayerLeave() {
Config.RevertSync();
}
```

## Finalizing and Publishing
It is recommended you inform **BepInEx** that you depend upon **CSync**.<br>
You can do this by adding a `BepInDependency` attribute and specifying the GUID of this library.
Expand All @@ -154,7 +69,7 @@ public class MyPlugin : BaseUnityPlugin

If you plan to upload your mod to **Thunderstore**, make sure you also specify the dependency within your `manifest.json` file by adding its **Thunderstore** ID to the array.
```json
"dependencies": ["BepInEx-BepInExPack-5.4.2100", "Owen3H-CSync-1.0.8"]
"dependencies": ["BepInEx-BepInExPack-5.4.2100", "Owen3H-CSync-2.0.0"]
```

::: info NOTE
Expand Down
Loading

0 comments on commit 698bcf3

Please sign in to comment.