vizor-games/InfraworldRuntime

View on GitHub
Source/InfraworldRuntime/Public/RpcClient.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 "UObject/NoExportTypes.h"


#include "Containers/Queue.h"
#include "Templates/SubclassOf.h"
#include "Templates/Atomic.h"

#include "RpcClientWorker.h"
#include "ChannelCredentials.h"

#include "RpcClient.generated.h"


DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRpcErrorSignature, URpcClient*, Dispatcher, const FRpcError&, Error);

/**
 * An RPC client used to interact with GRPC services from Blueprints and UE-compatible C++ code.
 * Being used as a base class for generated RPC clients, an RPC Client contains a set of methods to manage an RPC Channel.
 *
 * @note You should never instantiate it directly.
 *
 * You should use a Proto2Cpp converter to create GRPC wrappers for an every single service.
 */
UCLASS(Abstract, BlueprintType, Blueprintable)
class INFRAWORLDRUNTIME_API URpcClient : public UObject
{
    GENERATED_BODY()

    bool Init(const FString& URI, UChannelCredentials* ChannelCredentials);

public:
    URpcClient();
    virtual ~URpcClient();

    // Being called in implementations
    virtual void HierarchicalInit() PURE_VIRTUAL(URpcClient::HierarchicalInit,);

    // Being called in implementations
    virtual void HierarchicalUpdate() PURE_VIRTUAL(URpcClient::HierarchicalUpdate,);

    /**
     * Stops and disables this instance of RPC Client.
     * This operation is irreversible, you can't either send or receive requests and responses after doing so.
     *
     * @param bSynchronous
     *        Stop and wait a sec. Oh when you look at me like that my darling - What did you expect?
     *        If seriously - you should think thousand times before unchecking this option, because
     *        you can experience heavy and unpredictable racing hazard.
     */
    UFUNCTION(BlueprintCallable, Category="Vizor|RPC Client")
    void Stop(bool bSynchronous = true);

    /**
     * An update function, used to tell a Client, when to check queries and dispatch messages.
     * In widget (for example) you should call it every update (or redraw/invalidate).
     *
     * @note that frequency, you're calling this method won't ever affect the speed of message processing.
     * @deprecated No need to call this function anymore.
     */
    UFUNCTION(BlueprintCallable, Category = "Vizor|RPC Client", meta = (DeprecatedFunction, DeprecationMessage = "No need to call this function anymore: Updates now are being dispatched automatically"))
    void Update();

    /**
     * Checks whether the RPC Client could send requests.
     *
     * @return True if the RPC Client is properly initialized and can send requests. If not - all requests will be ignored.
     */
    UFUNCTION(BlueprintCallable, BlueprintPure, Category="Vizor|RPC Client", meta=(DisplayName="Can Send Requests?"))
    bool CanSendRequests() const;

    /**
     * Instantiates a new RPC Dispatcher. You should use this function, not 'Construct Object from Class', to properly initialize the instance.
     *
     * @param Class
     *        A subclass of RPC Client, that will be instantiated. Please don't select an abstract 'Rpc Client'.
     * @param InstantiationParameters
     *        Parameters, will be used for instantiation,
     * @param Outer
     *        An outer object, None (or empty) is valid as well, if so, GetTransientPackage() will be used to retrieve a static outer.
     * @return A newly created instance of RPC Client.
     */
    UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Vizor|RPC Client", meta=(DisplayName="Create RPC Client", DeterminesOutputType="Class", DeprecatedFunction, DeprecationMessage="Use function, accepting URI and ChannelCredentials instead"))
    static URpcClient* CreateRpcClient(TSubclassOf<URpcClient> Class, FRpcClientInstantiationParameters InstantiationParameters, UObject* Outer = nullptr);
    
    /**
     * Instantiates a new RPC Dispatcher. You should use this function, not 'Construct Object from Class', to properly initialize the instance.
     *
     * @param Class
     *        A subclass of RPC Client, that will be instantiated. Please don't select an abstract 'Rpc Client'.
     * @param URI
     *        Endpoint URI, will be used to establish connection.
     *        Must not start with 'http://' or 'https://' or any URL scheme.
     *        Must not have URL path, such as 'www.hello.eu/paths/are/not/allowed'
     *        Must be either a domain name or an IP address with or without an explicit port. 80'th port is used by default.
     * @param ChannelCredentials
     *        Credentials to use for the created RPC client.
     * @param Outer
     *        An outer object, None (or empty) is valid as well, if so, GetTransientPackage() will be used to retrieve a static outer.
     * @return A newly created instance of RPC Client.
     */
    UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Vizor|RPC Client", meta=(DisplayName="Create RPC Client", DeterminesOutputType="Class"))
    static URpcClient* CreateRpcClientUri(TSubclassOf<URpcClient> Class, const FString& URI, UChannelCredentials* ChannelCredentials, UObject* Outer = nullptr);

    /**
     * Called when did received any kind of error.
     */
    UPROPERTY(BlueprintAssignable, Category="Vizor|RPC Client", meta=(DisplayName="Event RPC Error"))
    FRpcErrorSignature EventRpcError;

protected:
    /** A pointer to an inner RpcClientWorker sending and receiving messages */
    TUniquePtr<RpcClientWorker> InnerWorker;

private:
    virtual void BeginDestroy() override;

    /** Whether the RPC Client could send requests or not */
    bool bCanSendRequests = false;

    /** A thread, where RPC client worker will reside */
    TAtomic<FRunnableThread*> Thread = { nullptr };

    /** An accumulator for error messages */
    TQueue<FRpcError> ErrorMessageQueue;

    /**
     * Global engine ticker handler
     * Only "IsValid() -> true" if this RPC client "CanSendRequests() -> true"
     */
    FDelegateHandle TickDelegateHandle;
};

template <class T>
FORCEINLINE T* NewRpcClient(const FString& URI, UChannelCredentials* ChannelCredentials, UObject* Outer = nullptr)
{
    static_assert(TIsDerivedFrom<T, URpcClient>::IsDerived, "T must derive URpcClient");
    static_assert(!TIsSame<T, URpcClient>::Value, "T must derive URpcClient, but mustn't be a bare URpcClient");

    return Cast<T>(URpcClient::CreateRpcClientUri(T::StaticClass(), URI, ChannelCredentials, Outer));
}