自定義 DataManipulators
自定義資料的核心是 DataManipulator 。在實作之前,你必須先決定是否需要為自定義資料建立一個分離的 API。一般而言最好將 API 與實作分離(如同 SpongeAPI),但是如果不會被其他開發者使用你也可以將兩者放在同一個類別。
你可能会为你的数据的每一个单元定义一些 API 方法,比如说 String
、 int
、 ItemStack 、或者一个自定义的类型如 Home
。这些小单元会被包装成 Value 的形式,从而可以通过数据键( Key )的方式访问。一个 Value
可能会针对不同对象,而展现出不同的扩展形式,比如说 MapValue 提供基础的键值对操作, BoundedValue 限定了一个数据的上下界(比如说整数的上下界)。对一个数据是否过界的判定基于 Comparator 类完成。
现在我们考虑你需要继承实现的 AbstractData 类型。虽然你可以从零开始,但这些类型已经为你减少了 很多 诸如实现需要的方法的工作。你可以在 org.spongepowered.api.data.manipulator.mutable.common 里找到一个完整的列表。请参阅 單一型態 或者 複合型態 以了解关于每一种类型的实现细节。
你需要創建兩個不同的類別, 一個是可變的版本,實作 DataManipulator 和你的抽象型態;以及不可變的版本,實作 ImmutableDataManipulator 和你的 不可變 抽象型態。
備註
全部 資料都必須有可變與不可變版本,你必須兩個都實作
對於所有型態,你都需要定義 DataManipulator#asImmutable() 和 asMutable() 方法,只需要簡單的複製目前物件到另一個版本的建構子即可。
数据值
你的相应 Getter 需要返回一个数据值,不过如下所示,我们为你提供了 ValueFactory 帮你代劳一部分工作。因为 Sponge 已经实现了一些 Value
对象,所以说这可以大大减少不必要的数据值类型。产生不同类型的数据值取决于使用不同的方法,如 createMapValue
、 createBoundedComparableValue
等。
程式碼範例:實作 Value Getter
import org.spongepowered.api.Sponge;
import org.spongepowered.api.data.value.ValueFactory;
import org.spongepowered.api.data.value.mutable.Value;
import org.spongepowered.cookbook.myhomes.data.home.Home;
import org.spongepowered.cookbook.myhomes.data.Keys;
@Override
protected Value<Home> defaultHome() {
return Sponge.getRegistry().getValueFactory()
.createValue(Keys.DEFAULT_HOME, getValue(), null);
}
請注意 ImmutableDataManipulator
必須回傳一個 ImmutableValue
,可以對回傳的 Value
呼叫 asImmutable()
。我們建議你對不可變版本的值做快取(例如類別的資料成員)。
每個 Value
都需要一個 Key 來識別它,像是在範例中的 Keys.DEFAULT_HOME
。與 value 相似,你可以使用 KeyFactory 的 makeXKey()
方法來為你的 value 創建一個 Key
你需要传入一个 TypeToken
用于代表你的数据值包装的 原生 类型,以及另一个 TypeToken
代表数据值类型本身。你同时需要提供一个 DataQuery ——这通常用于序列化你的数据值。还要为你的数据类型提供一个唯一的标识 ID 及名称。把这些放到一起你就可以得到一个可用于你的 Value
的 Key
了。
程式碼範例:創建 Key
import org.spongepowered.api.data.DataQuery;
import org.spongepowered.api.data.key.Key;
import org.spongepowered.api.data.key.KeyFactory;
import org.spongepowered.api.data.value.mutable.Value;
import org.spongepowered.api.data.value.mutable.Value;
import com.google.common.reflect.TypeToken;
import org.spongepowered.cookbook.myhomes.data.home.Home;
public static final Key<Value<Home>> DEFAULT_HOME = KeyFactory.makeSingleKey(
TypeToken.of(Home.class),
new TypeToken<Value<Home>>() {},
DataQuery.of("DefaultHome"), "myhomes:default_home", "Default Home");
備註
作为一个保留泛型的类型引用的实现,Sponge 在 TypeTokens 类中内置了一大串能够用到的 TypeToken 。
如果你需要自己創建一個,你可以透過兩種方式:
對於非泛型型態,使用
TypeToken.of(MyType.class)
對於泛型型態,可以建立匿名類別
TypeToken<MyGenericType<String>>() {}
序列化
如果你希望你的数据可 serializable 到 DataHolder 或者配置文件中,你必须还要实现 DataSerializable#toContainer() 方法。我们十分建议你调用父类的 super.toContainer()
,这样子就会包含 DataSerializable#getContentVersion() 提供的版本号信息。每次数据信息的结构发生变化时你应该提升你的版本号信息,并使用 DataContentUpdater 以保证向前兼容。
備註
這對於簡易單一型態是不需要的,因為它已經定義了 toContainer()
程式碼範例: 實作 toContainer
import org.spongepowered.api.data.DataContainer;
import org.spongepowered.cookbook.myhomes.data.Keys;
@Override
public DataContainer toContainer() {
DataContainer container = super.toContainer();
// This is the simplest, but use whatever structure you want!
container.set(Keys.DEFAULT_HOME.getQuery(), this.defaultHome);
container.set(Keys.HOMES, this.homes);
return container;
}
註冊
通过注册你自定义的 DataManipulator
,你可以让 Sponge 或其他插件以一种通用的方式使用它。游戏的服务端或相应的插件可以为你的数据创建副本、并序列化或反序列化你的数据,而不需要对你代码中的类加以直接引用。
要註冊 DataManipulator
Sponge 提供了 DataRegistration#builder()。它會生成 DataRegistration 並自動註冊它。
備註
由于 Sponge 的数据 API 的性质,你 必须 要在初始化阶段中的特定阶段注册你的 DataManipulator
——通过监听 GameInitializationEvent ,如下例所示。如果你想要在该阶段结束后注册,那么游戏将抛出一个异常。
import org.spongepowered.api.event.game.state.GameInitializationEvent;
import org.spongepowered.api.data.DataRegistration;
import org.example.MyCustomData;
import org.example.ImmutableCustomData;
import org.example.CustomDataBuilder;
@Listener
public void onInit(GameInitializationEvent event) {
DataRegistration.builder()
.dataClass(MyCustomData.class)
.immutableClass(ImmutableCustomData.class)
.builder(new CustomDataBuilder())
.manipulatorId("my-custom")
.dataName("My Custom")
.buildAndRegister(myPluginContainer);
}
警告
在 6.0.0
版本之前序列化的数据,或者你已经更换了 ID 的数据,将 不会 被自动识别出来,除非你使用了 DataManager#registerLegacyManipulatorIds(String, DataRegistration) 方法注册。如果你以 6.0.0
版本之前提供的方式注册了 DataManipulator
,那么相应的ID将会是 Class.getName() 的返回值,如 com.example.MyCustomData
。
單一型態
实现单一数据类型(Single Type)需要的工作要少些,因为很多方法都已经被你需要继承实现的 AbstractSingleData
类实现了。
「簡易」抽象型態最容易實作,但它僅限於下列型態:
Boolean
Comparable
Integer
List
Map
CatalogType
Enum
对于所有其他类型,你都需要通过继承实现 AbstractSingleData
类来实现它们。这允许你定义你自己的单一数据类型,而不受数据类型的约束,不过你就需要做较多的工作了。
小訣竅
默认的实现类已经在构造方法中把你的对象准备好了。你可以通过 getValue()
或者 getValueGetter()
方法获取到它。
簡易單一型態
幾乎所有工作都以經完成了。你需要做的只有:
繼承對應的抽象型態
對應 Key 到你的資料、物件本身、以及建構子的預設物件(如果物件為 null)
AbstractBoundedComparableData (及對應的不可變版本)還需要用於比較的最大及最小值,以及對應的 Comparator 。
備註
List
和 Mapped
單一型態必須使用 ListData
和 MappedData
(或對應的不可變版本)。這會為 DataManipulator
增加類似於操作 Map 或 List 的方法。
下列三個方法必須定義在可變的 manipluators 中:
fill(DataHolder, MergeFunction)
應該使用 MergeFunction#merge()
將提供的 DataHolder
取代你物件中的資料。
import org.spongepowered.api.data.DataHolder;
import org.spongepowered.api.data.merge.MergeFunction;
import org.spongepowered.cookbook.myhomes.data.friends.FriendsData;
import java.util.Optional;
@Override
public Optional<FriendsData> fill(DataHolder dataHolder, MergeFunction overlap) {
FriendsData merged = overlap.merge(this, dataHolder.get(FriendsData.class).orElse(null));
setValue(merged.friends().get());
return Optional.of(this);
}
from(DataContainer)
應該從 container 中取得並覆蓋 value ,如果不存在則回傳 Optional.empty()
import org.spongepowered.api.data.DataContainer;
import org.spongepowered.api.data.DataQuery;
import org.spongepowered.cookbook.myhomes.data.Keys;
import org.spongepowered.cookbook.myhomes.data.friends.FriendsData;
import org.spongepowered.cookbook.myhomes.data.friends.ImmutableFriendsData;
import com.google.common.collect.Maps;
import java.util.Optional;
import java.util.UUID;
@Override
public Optional<FriendsData> from(DataContainer container) {
if(container.contains(Keys.FRIENDS)) {
List<UUID> friends = container.getObjectList(Keys.FRIENDS.getQuery(), UUID.class).get();
return Optional.of(setValue(friends));
}
return Optional.empty();
}
copy()
顧名思義,應該回傳相同資料的副本。
import org.spongepowered.cookbook.myhomes.data.friends.FriendsData;
@Override
public FriendsData copy() {
return new FriendsDataImpl(getValue());
}
自定義單一型別
除此之外,你還需要重新定義以下方法:
getValueGetter()
應該會傳遞代表你資料的 Value
(見上文)。
toContainer()
應該會回傳含有你資料的 DataContainer
(見上文)。
複合型態
和只能提供一种数据值的单一数据类型相比,复合数据类型可以提供多个“复合”的数据值。如果多个数据值是强关联的,像 FurnaceData 一样,那么复合数据类型便可大显身手,不过,实现复合数据类型,也就会变得更复杂一些。
首先请定义你的数据操纵器的所有数据值的 Getter。对于每一个数据值,请再创建相应的 裸露在外的 对象的 Getter 和 Setter。你马上就会用到它们。对于不可变数据操纵器来说,只有 Getter 是需要实现的。
註冊 Values
然后你需要执行注册操作,这样基于 数据键 的系统才能在其上工作。如欲注册,请分别针对可变和不可变数据操纵器实现 DataManipulator#registerGettersAndSetters() 或 ImmutableDataManipulator#registerGetters() 方法。
對所有 value 你必須呼叫:
registerKeyValue(Key, Supplier)
傳入對應 key 的Value
getterregisterFieldGetter(Key, Supplier)
傳入上面定義的原始物件 getter 方法registerFieldSetter(Key, Consumer)
傳入 setter 方法,如果你正在實作可變版本
我們建議使用 Java 8 的 ::
語法來簡化 Supplier
和 Consumer
函數
程式碼範例:實作 Getters 與 Setters
import org.spongepowered.cookbook.myhomes.data.Keys
// registerGetters() for immutable implementation
@Override
protected void registerGettersAndSetters() {
registerKeyValue(Keys.DEFAULT_HOME, this::defaultHome);
registerKeyValue(Keys.HOMES, this::homes);
registerFieldGetter(Keys.DEFAULT_HOME, this::getDefaultHome);
registerFieldGetter(Keys.HOMES, this::getHomes);
// Only on mutable implementation
registerFieldSetter(Keys.DEFAULT_HOME, this::setDefaultHome);
registerFieldSetter(Keys.HOMES, this::setHomes);
}
fill(DataHolder, MergeFunction)
與 from(DataContainer)
的實作和單一型態類似,但必須處理所有的 values