Niestandardowe DataManipulators
Główną część niestandardowych danych stanowi :javadoc: DataManipulator. Aby ją zastosować, musisz najpierw zdecydować, czy chcesz utworzyć oddzielne API dla swoich niestandardowych danych. Ogólnie mówiąc najlepiej jest oddzielić API od realizacji (tak jak robi to SpongeAPI), ale jeśli nie będzie to widoczne dla innych programistów, możesz najzwyczajniej umieścić obie w tej samej klasie.
You’ll want to define an API method for each „unit” your data, such as a String
, int
, ItemStack or
a custom type like Home
. These units will be wrapped in a Value, which will allow it to be accessed
with Keys. There are various extensions of Value
depending on which object will be represented, such
as MapValue which provides the standard map operations, or BoundedComparableValue which can set
limits on the upper and lower bound of an Comparable objects like integers.
Now, pick which of the AbstractData types you’ll extend from. While you could implement from scratch, these abstract types remove a lot of the work that needs to be done implementing the required methods. A full list can be found in org.spongepowered.api.data.manipulator.mutable.common. See either Pojedyncze Typy or Typy złożone below for implementation details each type.
Musisz utworzyć dwie różne klasy - jedna, która jest zmienną i implementuje: javadoc: DataManipulator i twój abstrakcyjny typ oraz niezmienną wersję, która implementuje :javadoc:` ImmutableDataManipulator` oraz twój *niezmienny * typ abstrakcyjny.
Informacja
Wszystkie dane muszą mieć zmienne i niezmienne wersje, musisz zaimplementować obydwie.
For all types, you’ll need to define the DataManipulator#asImmutable()/ asMutable() methods - this is as simple as copying the existing objects into a constructor for the alternate version.
Wartości
Your value getter(s) need to return a value. In the example below, we get the ValueFactory. This saves us a
lot of type by using Sponge’s already implemented Value
objects. Depending on what value you’re creating there a
different methods to call such as createMapValue
, createBoundedComparableValue
, etc.
Przykład Kodu: Wykonywanie wartości 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);
}
Note that an ImmutableDataManipulator
would instead return an ImmutableValue
, by calling asImmutable()
on
the returned Value
. We recommended that you cache this (such as with a class field) in the immutable version.
Each Value
also needs a Key to identify it, seen in the example as Keys.DEFAULT_HOME
. Similar
to values, you use one of the makeXKey()
methods in KeyFactory to create a Key
for your value.
You need to pass one TypeToken
representing the raw type of your value, and one TypeToken
representing the
Value
. You also need to provide a DataQuery path - this is most commonly used to serialize the
Value
. As with any catalog type you must also provide a unique ID and a name. Put this all together and you have a
Key
you can use in your Value
s.
Przykład kodu: Tworzenie klucza
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");
Informacja
TypeTokens are used by the server implementation to preserve the generic type of your values. They are created in one of two ways:
For non-generic types, use
TypeToken.of(MyType.class)
For generic types, create an anonymous class with
TypeToken<MyGenericType<String>>() {}
Serializacja
To make your data serializable to DataHolders or config files, you must also
implement DataSerializable#toContainer(). We recommend calling super.toContainer()
as this will
include the version from DataSerializable#getContentVersion(). You should increase the version each time a
change is made to the format of your serialized data, and use DataContentUpdaters to allow backwards compatability.
Informacja
This is not required for simple single types, as the already implement toContainer()
Code Example: Implementing 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;
}
Pojedyncze Typy
Single types require little implementation because much of the work has already been done in the AbstractSingleData
type you extend from.
„Proste” typy abstrakcyjne są najprostsze do implementacji, ale są ograniczone tylko do poniższych typów:
zero-jedynkowe
Comparable
Całość
Lista
Mapa
TypKatalogu
Enum
Dla wszystkich innych typów musisz zaimplementować niestandardowy pojedynczy typ, rozszerzający `` AbstractSingleData``. Dzięki temu możliwe jest zdefiniowanie własnych pojedynczych danych przy użyciu dowolnego typu, podczas dalszego wykonywania większości pracy za ciebie.
Wskazówka
The abstract implementations save the object for you in the constructor. You can access it in your implementation
by calling the getValue()
and getValueGetter()
methods.
Proste pojedyńcze typy
Prawie cała prace jest wykonywana za ciebie dzięki prostym typom abstrakcyjnym. Wszystko, co musisz zrobić, to:
Rozszerz stosowny typ abstraktu
podaj
klucz
do twoich danych, samego obiektu i domyślnych obiektów (jeśli obiekt jest nieważny) w konstruktorze
AbstractBoundedComparableData (and the immutable equivalent) additionally require minimum and maximum values that will be checked, as well as a Comparator.
Informacja
List
and Mapped
single types must instead implement ListData
/ MappedData
(or the immutable
equivalent). This adds additional methods to allow Map-like/List-like behavior directly on the DataManipulator
.
Następujące 3 metody muszą być zdefiniowane w zmiennych manipulatorach:
fill(DataHolder, MergeFunction)
should replace the data on your object with that of the given DataHolder
,
using the result of MergeFunction#merge()
.
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)
should overwrite its value with the one in the container and return itself, otherwise return
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()
should, as the name suggests, return a copy of itself with the same data.
import org.spongepowered.cookbook.myhomes.data.friends.FriendsData;
@Override
public FriendsData copy() {
return new FriendsDataImpl(getValue());
}
Niestandardowe pojedyncze typy
Dodatkowo musisz zastąpić następujące metody:
getValueGetter()
should pass the Value
representing your data (see above).
toContainer()
should return a DataContainer
representing your data (see above).
Typy złożone
Whereas single types only support one value, „compound” types support however many values you want. This is useful when multiple objects are grouped, such as FurnaceData. The downside, however, is that they are more complex to implement.
To start with, create all the Value
getters that your data will have. For each value, create a method to get and
set the raw object, which you’ll use later. For immutable data, only the getters are necessary.
Rejestrowanie wartości
Next, you’ll want to register these so that the Keys-based system can reference them. To do this, implement either DataManipulator#registerGettersAndSetters() or ImmutableDataManipulator#registerGetters() depending on whether the data is mutable or not.
Dla każdej wartości musisz zadzwonić:
registerKeyValue(Key, Supplier)
referencing theValue
getter for the given keyregisterFieldGetter(Key, Supplier)
referencing the getter method for the raw object defined aboveregisterFieldSetter(Key, Consumer)
referencing the setter method above if you are implementing the mutable version
We recommend using Java 8’s ::
syntax for easy Supplier
and Consumer
functions.
Code Example: Implementing Getters and 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)
and from(DataContainer)
are similar to the implementations for single data,
but loading all your values.