커스텀 클래스 직렬화 방법¶
참고
다음은 고급 기능으로, C++ 코드 편집이 필요합니다.
릴레이 섹션에서 설명했듯이 릴레이 메시지는 Strix 클래스를 가져와서 네트워크를 통해 다른 클라이언트에게 보낼 수 있습니다.
그러나, 이렇게 하려면 클래스에 직렬화가 가능한 속성이 있어야 합니다. 커스텀 클래스에는 Strix가 직렬화하지 못하는 커스텀 타입이 더 있을 수도 있습니다. 이것들을 Strix 클래스에 다시 팩토링하기보다는 이 클래스용으로 커스텀 직렬화와 비직렬화를 지정하는 것이 더 합리적일 수도 있습니다.
아래 가이드에 이 프로세스가 설명되어 있습니다. 다소 길고 boilerplate 코드도 많습니다.
클래스에 클래스 ID를 부여합니다.
그 클래스에 래퍼 개체 구현을 추가합니다.
커스텀 직렬화와 비직렬화 함수를 지정합니다.
개체 어댑터를 추가합니다.
(UCLASS일 경우) MetaClass 전문화를 추가합니다.
(UCLASS일 경우) MetaClass 초기화 콜을 추가합니다.
클래스를 Strix Relay Arg로 이용할 수 있게 만듭니다.
클래스 정의¶
// BearingAndRange.h
// A class representing bearing angle and range.
// This class is a UCLASS as we want it to be usable in Blueprints.
// The STRIXSDK_API can be substituted for your specific API.
UCLASS(BlueprintType)
class STRIXSDK_API UBearingAndRange : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere)
float Bearing;
UPROPERTY(VisibleAnywhere)
int Range;
};
클래스 ID¶
// UEObjectSerializers.h
// Base UE Classes
const int FVectorClassId = -30;
const int FRotatorClassId = -31;
const int FTransformClassId = -32;
const int FStringClassId = -33;
const int FTextClassId = -34;
const int FQuatClassId = -35;
// ...
// Custom Class ID
const int BearingAndRangeClassID = -90;
UEObjectSerializers.h에는 래퍼 클래스별로 클래스 ID가 들어 있습니다. 서버에서 또는 비직렬화 시에 클래스를 구별하기 위해 이용하는 것입니다. 서로 중복되면 안 되므로 Strix 플러그인이 업데이트된다면 충돌하지 않도록 주의해야 합니다.
래퍼 개체 Impl¶
// UEObjectSerializers.h
typedef ComparableWrapperObjectImpl<FVector, FVectorClassId> FVectorObject;
typedef ComparableWrapperObjectImpl<FRotator, FRotatorClassId> FRotatorObject;
typedef ComparableWrapperObjectImpl<FString, FStringClassId> FStringObject;
// ...
// Custom class wrapper
typedef ComparableWrapperObjectImpl<BearingAndRange*, BearingAndRangeClassID> BearingAndRangeObject;
Strix ComparableWrapperObjectImpl 타입은 SDK에서 개체 래핑에 사용됩니다. 이 래퍼는 Strix가 개체를 비교할 수 있는 함수가 됩니다.
참고
이것은 래핑하고 있는 개체이므로 여기서는 포인터 타입을 래핑합니다.
직렬화¶
래퍼 개체가 만들어지면 Strix가 그 개체를 직렬화하고 비직렬화하는 방법을 알아야 합니다. 이것은 관련 메서드를 지정하는 SerializerImpl 클래스로 합니다.
// UEObjectSerializers.h
template<>
class strix::net::serialization::serializer::SerializerImpl<FVector, false>
{
public:
bool Serialize(strix::net::io::Encoder &encoder, const FVector &value)
{
bool res = encoder.WriteFloat(value.X);
res &= encoder.WriteFloat(value.Y);
res &= encoder.WriteFloat(value.Z);
return res;
}
bool Deserialize(io::Decoder &decoder, FVector &value) {
bool res = decoder.ReadFloat(value.X);
res &= decoder.ReadFloat(value.Y);
res &= decoder.ReadFloat(value.Z);
return res;
}
};
// ...
// Custom serialize/deserialize specialization.
template<>
class strix::net::serialization::serializer::SerializerImpl<UBearingAndRange, false>
{
public:
// The Serialize method takes a const reference to an object of type T
// and an encoder and serializes the object.
bool Serialize(strix::net::io::Encoder &encoder, const UBearingAndRange &value)
{
// The individual values of the custom class are written out to the
// encoder. The results are & together to determine success.
bool res = encoder.WriteFloat(value.Bearing);
res &= encoder.WriteInt(value.Range);
return res;
}
// The Deserialize method takes a reference to an object of type T
// and a decoder and deserializes from the decoder into the object.
bool Deserialize(io::Decoder &decoder, UBearingAndRange &value) {
// The individual values of the custom class are read from the decoder.
// The decoders Read methods take a reference to assign the read values
// to.
// The results are & together to determine success.
bool res = decoder.ReadFloat(value.Bearing);
res &= decoder.ReadInt(value.Range);
return res;
}
};
참고
쓰기와 읽기 순서는 같아야 합니다. 즉, 먼저 쓴 값을 먼저 읽어야 합니다.
직렬화 코드는 반대편에서 클라이언트가 읽을 클래스를 원시값으로 사용해야 합니다. 이것은 대부분의 클래스에서 명확합니다.
스트링
스트링은 직렬화 목적으로도 쓰고 읽을 수 있습니다.
encoder.WriteString("Will be serialized");
// ---
std::string temp;
decoder.ReadString(temp);
컨테이너
컨테이너 클래스도 직렬화가 가능합니다.
template <typename T, typename InAllocator>
class strix::net::serialization::serializer::SerializerImpl<MyArray<T, InAllocator>, false>
{
public:
bool Serialize(strix::net::io::Encoder &encoder, const MyArray<T, InAllocator> &value) {
int size = value.MyArrayLength();
// WriteArrayBegin(int len) tells the encoder to expect an array of a length len.
if (!encoder.WriteArrayBegin(size))
return false;
// Loop over each item
for (size_t i = 0; i < size; ++i)
{
// Tell the encoder to expect a new array item.
if (!encoder.WriteArrayItemBegin(i))
return false;
// Serialize the item with its defined serializer.
// This can also be done manually here.
if (!SerializerImpl<T>().Serialize(encoder, value[i]))
return false;
// Tell the encoder the item has been written.
if (!encoder.WriteArrayItemEnd(i))
return false;
}
// Tell the encoder the array has been written.
if (!encoder.WriteArrayEnd())
return false;
return true;
}
bool Deserialize(strix::net::io::Decoder &decoder, MyArray<T, InAllocator> &value) {
int len;
// Read the beginning of the array. This sets len to be the length of the array.
if (!decoder.ReadArrayBegin(len))
return false;
// Allocate enough space in the custom array.
value.AllocateMyArray(len);
// Loop each item.
for (int i = 0; i<len; i++)
{
// Deserialize using the item's deserialize method.
T v;
if (!SerializerImpl<T>().Deserialize(decoder, v))
return false;
// Add the item to the array.
value.AddToMyArray(v);
}
// Check the end of the array has been reached.
if (!decoder.ReadArrayEnd())
return false;
return true;
}
};
개체 어댑터¶
커스텀 클래스도 어댑터를 부여할 수 있습니다. 이 클래스에는 커스텀 타입이 Strix 개체에서 운용할 수 있도록 필요한 코드를 제공합니다.
// UEObjectAdapter.h
UEObjectAdapter(FVector& val);
UEObjectAdapter(FRotator& val);
UEObjectAdapter(FTransform& val);
// ...
// Custom Object Adapter
UEObjectAdapter(BearingAndRange* val);
// UEObjectAdapter.cpp
UEObjectAdapter::UEObjectAdapter(FVector& val) : ObjectAdapter(std::make_shared<FVectorObject>(val)) {}
UEObjectAdapter::UEObjectAdapter(FRotator & val) : ObjectAdapter(std::make_shared<FRotatorObject>(val)) {}
UEObjectAdapter::UEObjectAdapter(FTransform & val) : ObjectAdapter(std::make_shared<FTransformObject>(val)) {}
// ...
// Adapter operates on the ComparableWrapperObjectImpl defined previously
UEObjectAdapter::UEObjectAdapter(BearingAndRange& val) : ObjectAdapter(std::make_shared<BearingAndRangeObject>(val)) {}
참고
어댑터는 이 클래스에서 ObjectAdapter에 래핑된 새 ComparableWrapperObjectImpl 인스턴스를 이용합니다.
MetaClassT¶
참고
이것은 UCLASS 클래스에는 필수입니다.
다음 MetaClass 코드는 Strix 메타 클래스를 지정합니다. 이에 따라 Strix는 Unreal NewObject 함수로 커스텀 클래스의 새 인스턴스 만드는 방법을 알 수 있습니다.
// BearingAndRange.h
namespace strix { namespace net { namespace object {
// All methods and arguments should be the same for all your custom types.
// The only difference should be the typename for the template parameters.
template <>
class MetaClassT<UBearingAndRange, false> : public MetaClassBaseT<UBearingAndRange>
{
public:
static const MetaClassT &Get() {
static const MetaClassT<UBearingAndRange, false> instance;
return instance;
}
// This is an important function and the reason we have to define this template.
// Unreal handles object creation, so we have to substitute a standard new
// method for the Unreal NewObject method for this class.
void *Create() const override {
return NewObject<UBearingAndRange>();
}
private:
MetaClassT() {
MetaClass::SetClassId(TypeNameFactory<UBearingAndRange>::GetTypeName(false));
MetaClass::Register(*this, GetClassId());
}
};
}}}
MetaClass 초기화¶
참고
이것은 UCLASS 클래스에는 필수입니다.
메타 클래스는 반드시 초기화해야 합니다. 초기화는 UEObjectSerializers.h에 있는 기존 함수에서 할 수 있습니다.
// UEObjectSerializers.h
class UEObjectsSerializers
{
public:
static void Init()
{
strix::net::object::MetaClassT<strix::net::object::FTransformObject>::Get();
strix::net::object::MetaClassT<strix::net::object::FQuatObject>::Get();
// Custom metaclass initialization
strix::net::object::MetaClassT<UBearingAndRange>::Get();
}
};
Strix Relay Arg¶
// StrixBlueprintFunctionLibrary.h
UFUNCTION(BlueprintPure, meta = (DisplayName = "ToStrixRelayArg (BearingAndRange)", CompactNodeTitle = "->", Keywords = "cast convert", BlueprintAutocast), Category = "StrixBPLibrary|Conversions")
static FStrixRelayArg Conv_BearingAndRangeToStrixRelayArg(UBearingAndRange* val);
// StrixBlueprintFunctionLibrary.cpp
FStrixRelayArg UStrixBlueprintFunctionLibrary::Conv_BearingAndRangeToStrixRelayArg(UBearingAndRange* val)
{
FStrixRelayArg arg(strix::net::object::UEObjectAdapter(val).Get());
return arg;
}
ObjectAdapter가 정의되고 나면 그 어떤 커스텀 클래스(이 경우 포인터) 인스턴스도 간단히 FStrixRelayArg로 변환할 수 있습니다.
위 경우에는 라이브러리에 어댑터 함수가 추가됩니다. 그러면 블루프린트 안에서도 이 변환이 가능합니다.
참고
변환 함수 중에는 ObjectAdapter(직렬화 가능하도록 기본 설정된 것)를 이용하는 것도 있고, UEObjectAdapter(Unreal 전용 클래스)를 이용하는 것도 있습니다.