vizor-games/InfraworldRuntime

View on GitHub
Source/InfraworldRuntime/Public/CastUtils.h

Summary

Maintainability
Test Coverage
/*
 * Copyright 2018 Vizor Games LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
#pragma once

#include "CoreMinimal.h"

#include <string>
#include <functional>
#include <chrono>

#include "GrpcIncludesBegin.h"

#include <google/protobuf/repeated_field.h>
#include <google/protobuf/map.h>
#include <grpc++/client_context.h>

#include "GrpcIncludesEnd.h"

// Should be imported to avoid long chrono-related instructions.
using std::chrono::milliseconds;
using std::chrono::system_clock;

//
// Generic casts. Should be specified in each header file for each generated structure.
//
namespace casts
{
    // ~~~~~ Unreal TMap and TArray, protobuf google::protobuf::RepeatedField and google::protobuf::Map) ~~~~~

    template<class OutT, class InT>
    using _UnrealMap = TMap<OutT, InT>;

    template<class OutT, class InT>
    using _ProtobufMap = google::protobuf::Map<OutT, InT>;

    template<class OutT>
    using _UnrealArray = TArray<OutT>;

    template<class OutT>
    using _ProtobufArray = google::protobuf::RepeatedField<OutT>;

    template<class OutT>
    using _ProtobufPtrArray = google::protobuf::RepeatedPtrField<OutT>;


    // ~~~~~ CAST PROTOTYPES (GENERIC) ~~~~~

    template<class OutT, class InT>
    FORCEINLINE OutT Proto_Cast(const InT &Item)
    {
        // WARNING!!!
        // If you're receiving an error message, telling you something like:
        //
        // error: no matching conversion for static_cast from 'xxx' to 'yyy'
        // return (OutT) t;
        //        ^~~~~
        // error: no matching function for call to 'Proto_Cast'
        // consider creating your own template specification of Proto_Cast<?>()
        // because by default it can only cast statically

        return static_cast<OutT>(Item);
    }

    // ~~~~~ CAST FUNCTIONS (MAPS) ~~~~~

    // TMap -> Protobuf Map
    template<class OutK, class OutV, class InK, class InV>
    FORCEINLINE _UnrealMap<OutK, OutV> Proto_MapCast(const _ProtobufMap<InK, InV>& Map)
    {
        _UnrealMap<OutK, OutV> OutMap;

        for (auto It = Map.cbegin(); It != Map.cend(); ++It)
            OutMap.Add(Proto_Cast<OutK>(It->first), Proto_Cast<OutV>(It->second));

        return OutMap;
    }

    // Protobuf Map -> TMap
    template<class OutK, class OutV, class InK, class InV>
    FORCEINLINE _ProtobufMap<OutK, OutV> Proto_MapCast(const _UnrealMap<InK, InV>& Map)
    {
        _ProtobufMap<OutK, OutV> OutMap;

        for (const TPair<InK, InV>& Pair : Map)
            OutMap.insert(google::protobuf::MapPair<OutK, OutV>(Proto_Cast<OutK>(Pair.Key), Proto_Cast<OutV>(Pair.Value)));

        return OutMap;
    }

    // ~~~~~ CAST FUNCTIONS (ARRAYS) ~~~~~

    template<class OutT, class InT>
    FORCEINLINE _ProtobufArray<OutT> Proto_ArrayCast(const _UnrealArray<InT>& Array)
    {
        // Allocate a protobuf array and reserve capacity
        _ProtobufArray<OutT> OutArray;
        OutArray.Reserve((int32)Array.Num());

        // Each item shall be individually casted to OutT
        for (const InT& Item : Array)
            OutArray.Add(Proto_Cast<OutT>(Item));

        return OutArray;
    }

    template<class OutT, class InT>
    FORCEINLINE _UnrealArray<OutT> Proto_ArrayCast(const _ProtobufArray<InT>& Array)
    {
        // Allocate a TArray<OutT> and reserve capacity
        _UnrealArray<OutT> OutArray;
        OutArray.Reserve((int32)Array.size());

        // Each item shall be individually casted to OutT
        std::for_each(Array.cbegin(), Array.cend(), [&OutArray](InT Item) {
            OutArray.Add(Proto_Cast<OutT>(Item));
        });

        return OutArray;
    }

    // Overload for _ProtobufPtrArray (google::protobuf::RepeatedPtrField<?>)
    // _ProtobufPtrArray (aka google::protobuf::RepeatedPtrField<OutT>)
    // is the same as RepeatedField<?>, but used for repeated strings or messages

    template<class OutT, class InT>
    FORCEINLINE _ProtobufPtrArray<OutT> Proto_PtrArrayCast(const _UnrealArray<InT>& Array)
    {
        // Allocate a protobuf array and reserve capacity
        _ProtobufPtrArray<OutT> OutArray;
        OutArray.Reserve((int32)Array.Num());

        // Each item shall be individually casted to OutT
        for (const InT& Item : Array)
        {
            const OutT& cast_result = Proto_Cast<OutT>(Item);
            
            // We need to create a new instance of OutT to be posessed by the OutArray.
            OutArray.AddAllocated(new OutT(cast_result));
        }

        return OutArray;
    }

    template<class OutT, class InT>
    FORCEINLINE _UnrealArray<OutT> Proto_PtrArrayCast(const _ProtobufPtrArray<InT>& Array)
    {
        // Allocate a TArray<OutT> and reserve capacity
        _UnrealArray<OutT> OutArray;
        OutArray.Reserve((int32)Array.size());

        // Each item shall be individually casted to OutT
        std::for_each(Array.cbegin(), Array.cend(), [&OutArray](InT Item) {
            OutArray.Add(Proto_Cast<OutT>(Item));
        });

        return OutArray;
    }

    // Casting enums value-wise does not require any specialization. Thou can override the template function.
    template<typename OutT, typename InT>
    FORCEINLINE OutT Proto_EnumCast(const InT &Item)
    {
        return static_cast<OutT>((int)Item);
    };

    // ~~~~~ CAST FUNCTIONS (BYTE ARRAY), in protobuf byte arrays are std::strings ~~~~~

    template <>
    FORCEINLINE FByteArray Proto_Cast(const std::string& String)
    {
        // Allocate a TArray<uint8>
        TArray<uint8> OutArray;
        
        // Put String's content into the array. We can not (and do not need) to cast away const qualifier.
        OutArray.Insert(reinterpret_cast<const uint8*>(String.c_str()), String.size(), 0);

        // Finally, wrap all data into FByteArray
        return FByteArray(OutArray);
    }

    template <>
    FORCEINLINE std::string Proto_Cast(const FByteArray& Item)
    {
        const TArray<uint8>& Arr = Item.Bytes;
        return std::string(reinterpret_cast<const char*>(Arr.GetData()), Arr.Num());
    }

    // ~~~~~ CAST FUNCTIONS (UNREAL STRING and PROTOBUF STRING) ~~~~~

    template <>
    FORCEINLINE std::string Proto_Cast(const FString& String)
    {
        return std::string(TCHAR_TO_UTF8(*String));
    }

    template <>
    FORCEINLINE FString Proto_Cast(const std::string& String)
    {
        return FString(String.c_str());
    }

    /**
     * Casts an UE4-compatible client context to the GRPC-compatible context.
     *
     * @note That grpc::ClientContext doesn't have a copy constructor, so grpc::ClientContext can not be returned from a
     *       ProtoCast<?>(). Thus the method should set an instance of grpc::ClientContext that already exist.
     * @param InContext Input UE4-compatible client context.
     * @param OutContext Output GRPC-compatible client context.
     */
    FORCEINLINE void CastClientContext(const FGrpcClientContext &InContext, grpc::ClientContext &OutContext)
    {
        // Cast and set metadata, checking for errors.
        for (const TPair<FString, FString>& Pair : InContext.Metadata)
        {
            if (Pair.Key.IsEmpty())
            {
                UE_LOG(LogTemp, Error, TEXT("Metadata key is empty for mapping '%s'->'%s' and thus won't be added to the client context. Behaviour is restricted by %s"),
                    *Pair.Key, *Pair.Value, TEXT("grpc/core/lib/surface/validate_metadata:80"));
            }
            else if (Pair.Key.StartsWith(":"))
            {
                UE_LOG(LogTemp, Error, TEXT("Metadata key statrs with ':' for mapping '%s'->'%s' and thus won't be added to the client context. Behaviour is restricted by %s"),
                    *Pair.Key, *Pair.Value, TEXT("grpc/core/lib/surface/validate_metadata:84"));
            }
            else
            {
                OutContext.AddMetadata(casts::Proto_Cast<std::string>(Pair.Key), casts::Proto_Cast<std::string>(Pair.Value));
            }

            // TODO: Add some other validation checks if necessary.
        }

        // Set deadline (Only if it has a positive value. It is -1 by default)
        if (InContext.DeadlineSeconds > .0f)
        {
            const int64 Milliseconds = static_cast<int64>(((double)InContext.DeadlineSeconds * 1000.0));
            OutContext.set_deadline(system_clock::now() + milliseconds(Milliseconds));
        }

        // Set boolean parameters
        OutContext.set_idempotent(InContext.bIdempotent);
        OutContext.set_cacheable(InContext.bCacheable);
        OutContext.set_wait_for_ready(InContext.bWaitForReady);

        // Set authority
        OutContext.set_authority(casts::Proto_Cast<std::string>(InContext.Authority));

        // Set Compression Algorithm
        OutContext.set_compression_algorithm(Proto_EnumCast<grpc_compression_algorithm>(InContext.GrpcCompressionAlgorithm));

        // Set Initial Metadata Corked
        OutContext.set_initial_metadata_corked(InContext.bInitialMetadataCorked);
    }

    FORCEINLINE void CastStatus(const grpc::Status& InStatus, FGrpcStatus& OutStatus)
    {
        OutStatus.ErrorCode = Proto_EnumCast<EGrpcStatusCode>(InStatus.error_code());
        OutStatus.ErrorMessage = Proto_Cast<FString>(InStatus.error_message());
        OutStatus.ErrorDetails = Proto_Cast<FString>(InStatus.error_details());
    }

    // Since we have no support for unsigned types in Blueprints, we need to

    template <>
    FORCEINLINE _UnrealArray<int32> Proto_ArrayCast(const _ProtobufArray<uint32>& Array)
    {
        // Allocate a TArray<OutT> and reserve capacity
        _UnrealArray<int32> OutArray;
        OutArray.Reserve((int32)Array.size());

        // Each item shall be individually casted to OutT
        std::for_each(Array.cbegin(), Array.cend(), [&OutArray](uint32 Item) {
            OutArray.Add(Proto_Cast<int32>(Item));
        });

        return OutArray;
    }

    template <>
    FORCEINLINE _ProtobufArray<uint32> Proto_ArrayCast(const _UnrealArray<int32>& Array)
    {
        // Allocate a protobuf array and reserve capacity
        _ProtobufArray<uint32> OutArray;
        OutArray.Reserve((int32)Array.Num());

        // Each item shall be individually casted to OutT
        for (const int32 Item : Array)
            OutArray.Add(Proto_Cast<uint32>(Item));

        return OutArray;
    }
}