-
Notifications
You must be signed in to change notification settings - Fork 0
Making Your Own SerializableInventory
KInventory provides a few default implementations for Minecraft's more useful inventories, but sometimes you need to make your own inventories, because of course I can't be expected to prepare for every use case. To that end, I've provided some tools to make your own SerializableInventory
s so that you can serialize your own inventories, or perhaps some of Minecraft's that I've overlooked (if that's the case, please make a pull request!). This guide will show you how to do that.
For this guide, let's assume that you want to serialize an inventory called FurnaceInventory
that looks something like this:
class FurnaceInventory(
val input: ItemStack,
val output: ItemStack,
val fuel: ItemStack,
) : Inventory {
/*...*/
}
The class may have other variables, but since the ones in the constructor are the ones that are needed to make the inventory, let's focus on them.
So now that we have a class to serialize, let's do just that: serialize it! We're going to create a serializable version of FurnaceInventory
that extends SerializableInventory
like so:
@Serializable
class SerializableFurnaceInventory (
val input: SerializableItemStack,
val output: SeriailizableItemStack,
val fuel: SerializableItemStack
) : SerializableInventory<FurnaceInventory>(listOf(input, output, fuel))
Here, we have serializable versions of the input
, output
, and fuel
parameters from FurnaceInventory
. We're also extending SerializableInventory
and giving it FurnaceInventory
for the type parameter (remember, T
is the non-serializable version of the inventory that the serializable version needs to be able to convert to). We then concatenate input
, output
and fuel
into a list before passing it to SerializableInventory`'s constructor.
To make the mod compile, we also need to make sure that the type argument is serializable. However, when working with inventories, you generally don't need to serialize the type argument itself, you just need to build an object of that type from serializable data, and Kotlinx.serialization doesn't recognise this yet (though there is an open issue on the subject on the GitHub), so as a workaround I've created a dummy serializer called NotSerializable
. To the compiler, NotSerializable
looks like it should work as a serializer, but if you actually try to use it it will throw an error.
Annotate the type variable like so:
@Serializable
class SerializableFurnaceInventory (
val input: SerializableItemStack,
val output: SeriailizableItemStack,
val fuel: SerializableItemStack
) : SerializableInventory<@kotlinx.serialization.Serializable(NotSerializable::class) FurnaceInventory>(listOf(input, output, fuel))
Now, if you remember from the page about how KInventory is structured (go read that if you haven't already), every SerializableInventory
needs to implement the abstract function toInventory()
, which will convert the SerializableInventory
to T
. Let's implement that function now.
override fun toInventory() {
return FurnaceInventory(input.toItemStack(), output.toItemStack(), fuel.toItemStack())
}
And there we go! That's all you need to create your very own SerializableInventory
. If you would like to use KInventory's tools to create a serializer for KInventory
, though, then keep reading.
This step is optional, but if you'd like to be able to serialize your inventories without needing to convert them to serializable form first, you're going to need to do a bit of extra work. You'll need two things a SurrogateInventorySerializer
and a SerializableInventoryCompanion
SerializableInventoryCompanion
is an interface with a method to create a SerializableInventory
from an Inventory. It has two type parameters, I
and S
. I
is the inventory that should be converted, and S
is I
's serializable counterpart. The interface has one function: getSerializable(from: I): S
, which, as mentioned earlier, creates a SerializableInventory
from an Inventory
.
While you can of course implement this anywhere you want, the recommended place to put it is on the companion object of a SerializableInventory.
For instance, in our furnace example:
class SerializableFurnaceInventory(/*...*/) {
companion object : SerializableInventoryCompanion<FurnaceInventory, SerializableFurnaceInventory> {
override fun getSerializable(from: FurnaceInventory): SerializableFurnaceInventory {
return SerializableFurnaceInventory(from.input.serializable(), from.output.serializable(), from.fuel.serializable())
}
}
}
Now that you have a companion for your serializable inventory, it's time for the main event: let's make a serializer.
KInventory's automatic serializer creation works on the principle of surrogate serialization. Kotlin's wiki page explains it a lot better than I can, but in essence a surrogate serializer is one where a class is converted into its serializable form automatically by its serializer, rather than by the user. It then serializes it using the serializer which is automatically created by the @Serializable
annotation on the serializable version of the class.
To make your own serializer, you should get an instance of SurrogateInventorySerializer
that relates to your SerializableInventory
. While you can just construct the class normally, I recommend making an object extend it so that you can easily reference it instead of having to make a new instance each time. The class is open for that purpose.
object FurnaceInventorySerializer : SurrogateInventorySerializer<I, S>(/*...*/)
Like SerializableInventoryCompanion
, SurrogateInventorySerializer
has two type parameters, I
and S
, which refer to an inventory and said inventory's serializable counterpart respectively. So for the furnace, we should use:
object FurnaceInventorySerializer : SurrogateInventorySerializer<FurnaceInventory, SerializableFurnaceInventory>(/*...*/)
The serializer also has two constructor parameters.
-
surrogate
is theKSerializer
ofS
. While it is possible to get a serializer from a type argument, it's not really a good idea and it's quite inflexible as well. So, the user needs to pass in the serializer by calling theS.serializer()
function (the code can't call it because it's an extension function). -
surrogateCompanion
, is theSerializableInventoryCompanion
ofI
and S. If, as I suggested, you implemented it on the companion object of
S, then you'll just be able to pass in
S.Companion`.
Putting all that together, we get this object:
object FurnaceInventorySerializer : SurrogateInventorySerializer<FurnaceInventory, SerializableFurnaceInventory>(
SerializableFurnaceInventory.serializer(), SerializableFurnaceInventory.Companion
)
Now that you have a custom serializer for the inventory type you're serializing, you may as well annotate it's type argument in SerializableInventory
with your new serializer, just in case someone actually tries to serialize it:
@Serializable
class SerializableFurnaceInventory (
val input: SerializableItemStack,
val output: SeriailizableItemStack,
val fuel: SerializableItemStack
) : SerializableInventory<@kotlinx.serialization.Serializable(FurnaceInventorySerializer::class) FurnaceInventory>(listOf(input, output, fuel))
And there we have it! One custom serializable inventory complete with a surrogate serializer. Well done. Now go forth and serialize! (or read the page about common errors).