Skip to content

Commit f6be7b8

Browse files
committed
Updated example.
Replaced InnerNetObjectAttribute with IgnoreInnerNetObjectAttribute, now automatically registers InnerNetObjects. Added more methods to load prefab.
1 parent 805cdbc commit f6be7b8

File tree

4 files changed

+109
-38
lines changed

4 files changed

+109
-38
lines changed
+39-11
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,71 @@
11
using System;
2+
using System.Threading.Tasks;
23
using Hazel;
34
using InnerNet;
45
using Reactor.Networking.Attributes;
5-
using UnityEngine;
66

77
namespace Reactor.Example;
88

9-
[InnerNetObject]
9+
// The `IgnoreInnerNetObject` attribute is used to prevent this custom InnerNetObject
10+
// from being automatically registered by Reactor.
11+
[IgnoreInnerNetObject]
1012
public class ExampleInnerNetObject : InnerNetObject
1113
{
12-
// Method one of retrieving a prefab.
13-
// Be sure to only use one method!
14+
// The `InnerNetObjectPrefab` attribute is used to define how the prefab for this
15+
// custom InnerNetObject is retrieved. The prefab is a template object that is used
16+
// when spawning instances of this object in the game.
17+
// There are three examples provided for retrieving the prefab:
18+
19+
// Example 1: Directly assign a prefab
20+
// This is the simplest method, where the prefab is assigned directly to a static field or property.
21+
[InnerNetObjectPrefab]
22+
public static InnerNetObject? PrefabField;
23+
// or
24+
[InnerNetObjectPrefab]
25+
public static InnerNetObject? PrefabProperty { get; set; }
26+
27+
// Example 2: Retrieve the prefab via a static method
28+
// This method allows for more complex logic to retrieve the prefab.
29+
// The method must be static and return an InnerNetObject (or a GameObject with an InnerNetObject component).
1430
[InnerNetObjectPrefab]
15-
public static InnerNetObject GetPrefabByInnerNetObject()
31+
public static InnerNetObject GetPrefab()
1632
{
1733
throw new NotImplementedException($"GetPrefab prefab retrieval not implemented!");
1834
}
1935

20-
// Method two of retrieving a prefab.
21-
// Be sure to only use one method!
36+
// Example 3: Retrieve the prefab asynchronously
37+
// This method is similar to Example 2 but allows for asynchronous operations,
38+
// such as loading assets from disk.
2239
[InnerNetObjectPrefab]
23-
public static GameObject GetPrefabByGameObject()
40+
public static async Task<InnerNetObject> GetPrefabAsync()
2441
{
2542
throw new NotImplementedException($"GetPrefab prefab retrieval not implemented!");
2643
}
2744

28-
// Must be defined; in InnerNetObject, the method is abstract, so if not, an error will be thrown.
45+
// The `HandleRpc` method is required abstract to handle Remote Procedure Calls (RPCs) for this object.
46+
// RPCs are used to communicate between clients and the server.
47+
// The `callId` parameter identifies the type of RPC, and the `reader` parameter provides the data.
2948
public override void HandleRpc(byte callId, MessageReader reader)
3049
{
50+
// Implement logic to handle specific RPCs based on the `callId`.
51+
// For example, you might switch on `callId` to handle different types of RPCs.
3152
}
3253

33-
// Must be defined; in InnerNetObject, the method is abstract, so if not, an error will be thrown.
54+
// The `Serialize` method is required abstract to serialize the state of this object into a `MessageWriter`.
55+
// This is used to synchronize the object's state across the network.
56+
// The `initialState` parameter indicates whether this is the first time the object is being serialized.
3457
public override bool Serialize(MessageWriter writer, bool initialState)
3558
{
59+
// Implement logic to write the object's state to the `writer`.
60+
// Return `true` if the state was serialized successfully, otherwise `false`.
3661
return false;
3762
}
3863

39-
// Must be defined; in InnerNetObject, the method is abstract, so if not, an error will be thrown.
64+
// The `Deserialize` method is required abstract to deserialize the state of this object from a `MessageReader`.
65+
// This is used to update the object's state based on data received from the network.
66+
// The `initialState` parameter indicates whether this is the first time the object is being deserialized.
4067
public override void Deserialize(MessageReader reader, bool initialState)
4168
{
69+
// Implement logic to read the object's state from the `reader`.
4270
}
4371
}

Reactor/Networking/Attributes/InnerNetObjectAttribute.cs Reactor/Networking/Attributes/IgnoreInnerNetObjectAttribute.cs

+67-24
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection;
5+
using System.Threading.Tasks;
56
using BepInEx.Unity.IL2CPP;
67
using HarmonyLib;
78
using InnerNet;
@@ -10,16 +11,16 @@
1011
namespace Reactor.Networking.Attributes;
1112

1213
/// <summary>
13-
/// Automatically registers a custom <see cref="InnerNetObject"/>.
14+
/// Ignores registering <see cref="InnerNetObject"/>.
1415
/// </summary>
1516
[AttributeUsage(AttributeTargets.Class)]
16-
public sealed class InnerNetObjectAttribute : Attribute
17+
public sealed class IgnoreInnerNetObjectAttribute : Attribute
1718
{
1819
private static readonly HashSet<Assembly> _registeredAssemblies = new();
19-
private static readonly HashSet<MethodInfo> _registeredMethods = new();
20+
private static readonly HashSet<MemberInfo> _registeredMembers = new();
2021

2122
/// <summary>
22-
/// Registers all <see cref="InnerNetObject"/>s annotated with <see cref="InnerNetObjectAttribute"/> in the specified <paramref name="assembly"/>.
23+
/// Registers all <see cref="InnerNetObject"/>s annotated with out <see cref="IgnoreInnerNetObjectAttribute"/> in the specified <paramref name="assembly"/>.
2324
/// </summary>
2425
/// <remarks>This is called automatically on plugin assemblies so you probably don't need to call this.</remarks>
2526
/// <param name="assembly">The assembly to search.</param>
@@ -30,31 +31,41 @@ public static void Register(Assembly assembly)
3031

3132
foreach (var type in assembly.GetTypes())
3233
{
33-
var attribute = type.GetCustomAttribute<InnerNetObjectAttribute>();
34+
if (!type.IsSubclassOf(typeof(InnerNetObject))) continue;
3435

35-
if (attribute != null)
36-
{
37-
if (!type.IsSubclassOf(typeof(InnerNetObject)))
38-
{
39-
throw new InvalidOperationException($"Type {type.FullDescription()} has {nameof(InnerNetObjectAttribute)} but doesn't extend {nameof(InnerNetObject)}.");
40-
}
36+
var attribute = type.GetCustomAttribute<IgnoreInnerNetObjectAttribute>();
4137

38+
if (attribute == null)
39+
{
4240
try
4341
{
44-
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
45-
46-
var prefabMethod = methods.FirstOrDefault(method =>
47-
method.GetCustomAttribute<InnerNetObjectPrefabAttribute>() != null &&
48-
(method.ReturnType == typeof(GameObject) || method.ReturnType == typeof(InnerNetObject)) &&
49-
method.IsStatic);
42+
var members = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
5043

51-
if (prefabMethod != null)
44+
var prefabMember = members.FirstOrDefault(member =>
5245
{
53-
_registeredMethods.Add(prefabMethod);
46+
if (member is MethodInfo method)
47+
{
48+
return method.IsStatic && (method.ReturnType == typeof(GameObject) || method.ReturnType == typeof(InnerNetObject) || method.ReturnType == typeof(Task<GameObject>) || method.ReturnType == typeof(Task<InnerNetObject>));
49+
}
50+
else if (member is FieldInfo field)
51+
{
52+
return field.IsStatic && (field.FieldType == typeof(GameObject) || field.FieldType == typeof(InnerNetObject));
53+
}
54+
else if (member is PropertyInfo property)
55+
{
56+
return property.GetMethod?.IsStatic == true && (property.PropertyType == typeof(GameObject) || property.PropertyType == typeof(InnerNetObject));
57+
}
58+
59+
return false;
60+
});
61+
62+
if (prefabMember != null)
63+
{
64+
_registeredMembers.Add(prefabMember);
5465
}
5566
else
5667
{
57-
Warning($"Failed to register InnerNetObject, static prefab return method not found.");
68+
Warning($"Failed to register InnerNetObject, static prefab return member not found.");
5869
}
5970
}
6071
catch (Exception ex)
@@ -65,19 +76,51 @@ public static void Register(Assembly assembly)
6576
}
6677
}
6778

68-
internal static void LoadRegistered()
79+
internal static async Task LoadRegisteredAsync()
6980
{
70-
if (_registeredMethods.Count > 0) // Increase array length by one because of beginning if check in InnerNetClient.CoHandleSpawn()
81+
if (_registeredMembers.Count > 0) // Increase array length by one because of beginning if check in InnerNetClient.CoHandleSpawn()
7182
{
7283
var innerNetClient = AmongUsClient.Instance;
7384
var list2 = innerNetClient.SpawnableObjects.ToList();
7485
list2.Add(new());
7586
innerNetClient.SpawnableObjects = list2.ToArray();
7687
}
7788

78-
foreach (var prefabMethod in _registeredMethods)
89+
foreach (var prefabMember in _registeredMembers)
7990
{
80-
var prefab = prefabMethod.Invoke(null, null);
91+
object? prefab = null;
92+
93+
if (prefabMember is MethodInfo method)
94+
{
95+
if (method.ReturnType == typeof(Task<GameObject>) || method.ReturnType == typeof(Task<InnerNetObject>))
96+
{
97+
if (method.Invoke(null, null) is Task task)
98+
{
99+
await task.ConfigureAwait(false);
100+
101+
if (method.ReturnType == typeof(Task<GameObject>))
102+
{
103+
prefab = ((Task<GameObject>) task).Result;
104+
}
105+
else if (method.ReturnType == typeof(Task<InnerNetObject>))
106+
{
107+
prefab = ((Task<InnerNetObject>) task).Result;
108+
}
109+
}
110+
}
111+
else
112+
{
113+
prefab = method.Invoke(null, null);
114+
}
115+
}
116+
else if (prefabMember is FieldInfo field)
117+
{
118+
prefab = field.GetValue(null);
119+
}
120+
else if (prefabMember is PropertyInfo property)
121+
{
122+
prefab = property.GetValue(null);
123+
}
81124

82125
if (prefab == null)
83126
{

Reactor/Networking/Attributes/InnerNetObjectPrefabAttribute.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Reactor.Networking.Attributes;
88
/// Attribute for Load prefab method for custom <see cref="InnerNetObject"/>.
99
/// </summary>
1010
/// <remarks>Must be static and return either <see cref="GameObject"/> with a <see cref="InnerNetObject"/> component, or a <see cref="InnerNetObject"/> prefab.</remarks>
11-
[AttributeUsage(AttributeTargets.Method)]
11+
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
1212
public sealed class InnerNetObjectPrefabAttribute : Attribute
1313
{
1414
}

Reactor/ReactorPlugin.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public ReactorPlugin()
5151
RegisterCustomRpcAttribute.Initialize();
5252
MessageConverterAttribute.Initialize();
5353
MethodRpcAttribute.Initialize();
54-
InnerNetObjectAttribute.Initialize();
54+
IgnoreInnerNetObjectAttribute.Initialize();
5555

5656
LocalizationManager.Register(new HardCodedLocalizationProvider());
5757
}
@@ -83,7 +83,7 @@ public override void Load()
8383
/// <inheritdoc />
8484
public void LateLoad()
8585
{
86-
InnerNetObjectAttribute.LoadRegistered();
86+
_ = IgnoreInnerNetObjectAttribute.LoadRegisteredAsync();
8787
}
8888

8989
/// <inheritdoc />

0 commit comments

Comments
 (0)