-
Notifications
You must be signed in to change notification settings - Fork 1
Modder guide : Serialization
Serialization is the way to convert entities of different complexity degrees, into numbers, that can be sent over the network. RimAlong uses BinaryFormatter, which, obviously, can't convert EVERYTHING into sequence of bytes. Thus, it needs help. And we can help him, by providing him ISerializationSurrogate
implementation. This might sound confusing, but essentially, it's just a thing, that tells WHAT and HOW should be sent and taken from serialization stream. Still sounds confusing? Actually, it's pretty simple. Here's how IntVec3
serialized :
public class IntVec3Surrogate : ISerializationSurrogate
{
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
IntVec3 v = (IntVec3)obj;
info.AddValue("xi", v.x);
info.AddValue("yi", v.y);
info.AddValue("zi", v.z);
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
IntVec3 v = new IntVec3();
v.x = info.GetInt32("xi");
v.y = info.GetInt32("yi");
v.z = info.GetInt32("zi");
return v;
}
}
Looks pretty simple, you might say. Also you might say, that it won't work with such sophisticated thing, like Pawn. But here's one very important concept :RimAlong implies that every entity of non-primitive type already exists at every player. In that way, we only need to send information, that's required to select exact same entity on different client, and use that as a parameter. You can see that by looking how Thing
, and every of it's derivees, getting serialized :
using System.Collections.Generic;
using System.Runtime.Serialization;
using Verse;
namespace CooperateRim
{
public class ThingSurrogate : ISerializationSurrogate
{
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
Thing p = (Thing)obj;
info.AddValue("pawn_thingid", p.thingIDNumber);
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
int idNumber = info.GetInt32("pawn_thingid");
List<Thing>[] things = (List<Thing>[])Find.CurrentMap.thingGrid.GetType().GetField("thingGrid", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(Find.CurrentMap.thingGrid);
foreach (var tl in things)
{
foreach (var thing in tl)
{
if (thing.thingIDNumber == idNumber)
{
return thing;
}
}
}
CooperateRimming.Log("Could not locate a pawn with thingid " + idNumber);
return null;
}
}
}
As you can see, what's sent is nothing but a thingID. And we just searching for the entity with the same thingID, in order to do something with it. This applies some limitations, tho. If you need to explicitly create something in response to user input, you actually need transfer data, required to create such entity across all clients, instead of creating it and sending it over the network. To see that, we need to check how bills are getting created :
using Harmony;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Text;
using Verse;
namespace CooperateRim
{
[HarmonyPatch(typeof(BillStack), "DoListing")]
class BillStackPatch
{
public static void MakeNewBillAt(Building_WorkTable table, RecipeDef recipe)
{
table.BillStack.AddBill(BillUtility.MakeNewBill(recipe));
}
[HarmonyPrefix]
public static void Prefix(ref Func<List<FloatMenuOption>> recipeOptionsMaker, ITab_Bills __instance)
{
Func<List<FloatMenuOption>> bill_source = recipeOptionsMaker;
Building_WorkTable selThing = (Building_WorkTable)Find.Selector.SingleSelectedThing;
List<RecipeDef> rdef = selThing.def.AllRecipes;
recipeOptionsMaker = () =>
{
List<FloatMenuOption> optList = bill_source();
int ii = 0;
foreach (FloatMenuOption opt in optList)
{
int iii = ii;
RecipeDef _rdef = rdef[ii];
opt.action = () => { CooperateRimming.Log("recipe option clicked for recipe " + _rdef); MakeNewBillAt(selThing, _rdef); };
ii++;
}
return optList;
};
}
}
}
Here, every FloatMenuOption
will actually replicate invocation of method, that will create bill, using Recipedef
, that already exists at every remote client (MakeNewBillAt
is "ParrotPatched").
After declaring the surrogate, you have to tell SerializationService
about that during mod initialization :
SerializationService.AppendSurrogate(typeof(Thing), new ThingSurrogate());
That's it! You can use Thing
as replicated call argument!