1+ using System ;
12using BepInEx . Configuration ;
23using BepInEx ;
34using System . Collections . Generic ;
5+ using System . Diagnostics . CodeAnalysis ;
46using System . IO ;
5- using HarmonyLib ;
7+ using System . Security . Cryptography ;
8+ using System . Text ;
9+ using CSync . Extensions ;
10+ using JetBrains . Annotations ;
11+ using Unity . Netcode ;
12+ using UnityEngine ;
613
714namespace CSync . Lib ;
815
916/// <summary>
1017/// Helper class enabling the user to easily setup CSync.<br></br>
1118/// Handles config registration, instance syncing and caching of BepInEx files.<br></br>
1219/// </summary>
20+ [ PublicAPI ]
1321public class ConfigManager {
14- internal static Dictionary < string , ConfigFile > FileCache = [ ] ;
15- internal static Dictionary < string , ISynchronizable > Instances = [ ] ;
22+ internal static readonly Dictionary < string , ConfigFile > FileCache = [ ] ;
23+ internal static readonly Dictionary < InstanceKey , ISyncedConfig > Instances = [ ] ;
1624
17- internal static ConfigFile GetConfigFile ( string fileName ) {
18- bool exists = FileCache . TryGetValue ( fileName , out ConfigFile cfg ) ;
19- if ( ! exists ) {
20- string absPath = Path . Combine ( Paths . ConfigPath , fileName ) ;
25+ private static event Action ? OnPopulateEntriesRequested ;
26+ internal static void PopulateEntries ( ) => OnPopulateEntriesRequested ? . Invoke ( ) ;
2127
22- cfg = new ( absPath , false ) ;
23- FileCache . Add ( fileName , cfg ) ;
24- }
28+ private static readonly Lazy < GameObject > LazyPrefab ;
29+ internal static GameObject Prefab => LazyPrefab . Value ;
30+
31+ static ConfigManager ( )
32+ {
33+ LazyPrefab = new Lazy < GameObject > ( ( ) =>
34+ {
35+ var container = new GameObject ( "CSyncPrefabContainer" )
36+ {
37+ hideFlags = HideFlags . HideAndDontSave
38+ } ;
39+ container . SetActive ( false ) ;
40+ UnityEngine . Object . DontDestroyOnLoad ( container ) ;
41+
42+ var prefab = new GameObject ( "ConfigSyncHolder" ) ;
43+ prefab . transform . SetParent ( container . transform ) ;
44+ var networkObject = prefab . AddComponent < NetworkObject > ( ) ;
45+ var hash = MD5 . Create ( ) . ComputeHash ( Encoding . UTF8 . GetBytes ( $ "{ MyPluginInfo . PLUGIN_GUID } :ConfigSyncHolder") ) ;
46+ networkObject . GlobalObjectIdHash = BitConverter . ToUInt32 ( hash ) ;
47+
48+ return prefab ;
49+ } ) ;
50+ }
51+
52+ internal static void AddToFileCache ( ConfigFile configFile )
53+ {
54+ FileCache . TryAdd ( configFile . GetConfigFileRelativePath ( ) , configFile ) ;
55+ }
56+
57+ internal static ConfigFile GetConfigFile ( string relativePath )
58+ {
59+ if ( FileCache . TryGetValue ( relativePath , out ConfigFile configFile ) )
60+ return configFile ;
2561
26- return cfg ;
62+ string absolutePath = Path . GetFullPath ( Path . Combine ( Paths . BepInExRootPath , relativePath ) ) ;
63+ configFile = new ( absolutePath , false ) ;
64+ FileCache . Add ( relativePath , configFile ) ;
65+ return configFile ;
2766 }
2867
2968 /// <summary>
3069 /// Register a config with CSync, making it responsible for synchronization.<br></br>
3170 /// After calling this method, all clients will receive the host's config upon joining.
3271 /// </summary>
33- public static void Register < T > ( T config ) where T : SyncedConfig < T > , ISynchronizable {
34- string guid = config . GUID ;
35-
36- if ( config == null ) {
37- Plugin . Logger . LogError ( $ "An error occurred registering config: { guid } \n Config instance cannot be null!") ;
72+ public static void Register < T > ( T config ) where T : SyncedConfig < T > {
73+ if ( config is null )
74+ {
75+ throw new ArgumentNullException ( nameof ( config ) , "Config instance is null, cannot register." ) ;
3876 }
3977
40- if ( Instances . ContainsKey ( guid ) ) {
41- Plugin . Logger . LogWarning ( $ "Attempted to register config `{ guid } ` after it has already been registered!") ;
42- return ;
78+ var assemblyQualifiedTypeName = typeof ( T ) . AssemblyQualifiedName ?? throw new ArgumentException ( nameof ( config ) ) ;
79+ var key = new InstanceKey ( config . GUID , assemblyQualifiedTypeName ) ;
80+
81+ try {
82+ Instances . Add ( key , config ) ;
4383 }
84+ catch ( ArgumentException exc ) {
85+ throw new InvalidOperationException ( $ "Attempted to register config instance of type `{ typeof ( T ) } `, but an instance has already been registered.", exc ) ;
86+ }
87+
88+ SyncedInstance < T > . Instance = config ;
89+ OnPopulateEntriesRequested += config . PopulateEntryContainer ;
4490
45- config . InitInstance ( config ) ;
46- Instances . Add ( guid , config ) ;
91+ var syncBehaviour = Prefab . AddComponent < ConfigSyncBehaviour > ( ) ;
92+ syncBehaviour . ConfigInstanceKey = key ;
4793 }
4894
49- internal static void SyncInstances ( ) => Instances . Values . Do ( i => i . SetupSync ( ) ) ;
50- internal static void RevertSyncedInstances ( ) => Instances . Values . Do ( i => i . RevertSync ( ) ) ;
51- }
95+ [ UsedImplicitly ]
96+ [ Serializable ]
97+ [ SuppressMessage ( "ReSharper" , "Unity.RedundantSerializeFieldAttribute" ) ] // they are *not* redundant!
98+ public readonly record struct InstanceKey ( string Guid , string AssemblyQualifiedName )
99+ {
100+ [ field: SerializeField ]
101+ public string Guid { get ; }
52102
53- public interface ISynchronizable {
54- void SetupSync ( ) ;
55- void RevertSync ( ) ;
56- }
103+ [ field : SerializeField ]
104+ public string AssemblyQualifiedName { get ; }
105+ }
106+ }
0 commit comments