Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support protobuf serializer for custom types #523

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
Add a stopwatch sign example
  • Loading branch information
LeeTwentyThree committed Feb 19, 2024
commit 91bcbac8a1d9a63c6604c8d25a5d1fe1a45ae0cd
98 changes: 91 additions & 7 deletions Example mod/SerializableBehaviourExample.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using BepInEx;
using Nautilus.Assets;
using Nautilus.Assets.PrefabTemplates;
using Nautilus.Handlers;
using ProtoBuf;
using UnityEngine;
Expand All @@ -11,20 +13,34 @@ namespace Nautilus.Examples;
internal class SerializableBehaviourExample : BaseUnityPlugin
{
// You need to store the generated methods so they can be used in your serialize and deserialize static methods
public static Action<RealtimeCounter, int, ProtoWriter> serializeMethodInfo;
public static Func<RealtimeCounter, ProtoReader, RealtimeCounter> deserializeMethodInfo;
public static Action<RealtimeCounter, int, ProtoWriter> realtimeCounterSerializeMethodInfo;
public static Func<RealtimeCounter, ProtoReader, RealtimeCounter> realtimeCounterDeserializeMethodInfo;
public static Action<StopwatchSignExample, int, ProtoWriter> stopwatchSignExampleSerializeMethodInfo;
public static Func<StopwatchSignExample, ProtoReader, StopwatchSignExample> stopwatchSignExampleDeserializeMethodInfo;

private void Awake()
{
// We first generate automatically the serialize and deserialize methods for our serializable type
var methodInfos = ProtobufSerializerHandler.GenerateSerializeAndDeserializeMethods<RealtimeCounter>();
var realtimeCounterMethodInfos = ProtobufSerializerHandler.GenerateSerializeAndDeserializeMethods<RealtimeCounter>();
// And we store them somewhere easily accessible
serializeMethodInfo = methodInfos.Item1;
deserializeMethodInfo = methodInfos.Item2;
realtimeCounterSerializeMethodInfo = realtimeCounterMethodInfos.Item1;
realtimeCounterDeserializeMethodInfo = realtimeCounterMethodInfos.Item2;

// Do it for the stopwatch
var stopwatchSignMethodInfos = ProtobufSerializerHandler.GenerateSerializeAndDeserializeMethods<StopwatchSignExample>();
stopwatchSignExampleSerializeMethodInfo = stopwatchSignMethodInfos.Item1;
stopwatchSignExampleDeserializeMethodInfo = stopwatchSignMethodInfos.Item2;

// Don't forget to register your serializable type with the right serialize and deserialize method (those which you created manually)
// You can pick whatever type ID (e.g. 3141592) you want as long it's not used by another mod or by Subnautica itself
ProtobufSerializerHandler.RegisterSerializableType<RealtimeCounter>(3141592, RealtimeCounter.Serialize, RealtimeCounter.Deserialize);
ProtobufSerializerHandler.RegisterSerializableType<StopwatchSignExample>(3141593, StopwatchSignExample.Serialize, StopwatchSignExample.Deserialize);

var stopwatchSign = new CustomPrefab(PrefabInfo.WithTechType("StopwatchSign"));
var stopwatchSignTemplate = new CloneTemplate(stopwatchSign.Info, TechType.Sign);
stopwatchSignTemplate.ModifyPrefab += go => go.AddComponent<StopwatchSignExample>();
stopwatchSign.SetGameObject(stopwatchSignTemplate);
stopwatchSign.Register();
}
}

Expand Down Expand Up @@ -85,7 +101,7 @@ public static void Serialize(RealtimeCounter realtimeCounter, int objTypeId, Pro
// In that situation, please pick the right WireType for your variables and adapt the following Write[Object] call

// In this case, we only have basic types to serialize so we just invoke the generated method
SerializableBehaviourExample.serializeMethodInfo.Invoke(realtimeCounter, objTypeId, writer);
SerializableBehaviourExample.realtimeCounterSerializeMethodInfo.Invoke(realtimeCounter, objTypeId, writer);
}

// Deserialize must be a "static YourType" with parameters (YourType, ProtoReader)
Expand All @@ -95,9 +111,77 @@ public static RealtimeCounter Deserialize(RealtimeCounter realtimeCounter, Proto
// Also the different cases must be using the same field numbers as defined in Serialize

// In this case, we only have basic types to serialize so we just invoke the generated method
realtimeCounter = SerializableBehaviourExample.deserializeMethodInfo.Invoke(realtimeCounter, reader);
realtimeCounter = SerializableBehaviourExample.realtimeCounterDeserializeMethodInfo.Invoke(realtimeCounter, reader);

// Return the same object you've been modifying
return realtimeCounter;
}
}

[ProtoContract]
internal class StopwatchSignExample : MonoBehaviour, IProtoEventListener
{
[ProtobufSerializerHandler.SubnauticaSerialized(1)]
public int version = 1;

[ProtobufSerializerHandler.SubnauticaSerialized(2)]
public float timePassed;

[ProtobufSerializerHandler.SubnauticaSerialized(3)]
public int serializations;

[ProtobufSerializerHandler.SubnauticaSerialized(4)]
public int deserializations;

private Sign _sign;

private void Start()
{
_sign = GetComponent<Sign>();
}

private void Update()
{
if (_sign) _sign.signInput.inputField.text = timePassed.ToString("#.0") + $"\nSerializations: {serializations}" + $"\nDeserializations: {deserializations}";
timePassed += Time.deltaTime;
}

// Below are two listeners from the optional interface IProtoEventListener:
// OnProtoDeserialize happens after this MonoBehaviour's GameObject full hierarchy is deserialized
public void OnProtoDeserialize(ProtobufSerializer serializer)
{
deserializations++;
}

// OnProtoSerialize happens before this MonoBehaviour's GameObject full hierarchy is serialized
public void OnProtoSerialize(ProtobufSerializer serializer)
{
version = 1;
serializations++;
}

// Below are the required static methods for Protobuf serializer to serialize and deserialize this MonoBehaviour
// You can find more examples in ProtobufSerializerPrecompiled just like Serialize11492366 and Deserialize11492366
// Serialize must be a "static void" with parameters (YourType, int, ProtoWriter)
public static void Serialize(StopwatchSignExample stopwatchSignExample, int objTypeId, ProtoWriter writer)
{
// If you only have basic types you only need to use the generated methods but you may need to adapt this with more complex types
// In that situation, please pick the right WireType for your variables and adapt the following Write[Object] call

// In this case, we only have basic types to serialize so we just invoke the generated method
SerializableBehaviourExample.stopwatchSignExampleSerializeMethodInfo.Invoke(stopwatchSignExample, objTypeId, writer);
}

// Deserialize must be a "static YourType" with parameters (YourType, ProtoReader)
public static StopwatchSignExample Deserialize(StopwatchSignExample stopwatchSignExample, ProtoReader reader)
{
// Here you need to use the methods Read[Object] accordingly to what you wrote in Serialize: Write[Object]
// Also the different cases must be using the same field numbers as defined in Serialize

// In this case, we only have basic types to serialize so we just invoke the generated method
stopwatchSignExample = SerializableBehaviourExample.stopwatchSignExampleDeserializeMethodInfo.Invoke(stopwatchSignExample, reader);

// Return the same object you've been modifying
return stopwatchSignExample;
}
}
Loading