You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
[WebRemoteControl] Add WebSocket routes to control when transactions are created
#jira: UE-151340 #rb: Alejandro.Arango #preflight 6286d359cf5ac7317c494b5a [CL 20289121 by Elliot Colp in ue5-main branch]
This commit is contained in:
@@ -696,10 +696,19 @@ struct FRCWebSocketPresetSetPropertyBody : public FRCRequest
|
||||
ERCModifyOperation Operation = ERCModifyOperation::EQUAL;
|
||||
|
||||
/**
|
||||
* Whether a transaction should be created for the call.
|
||||
* How to handle generating transactions for this property change.
|
||||
* If NONE, don't generate a transaction immediately.
|
||||
* If AUTOMATIC, let the Remote Control system automatically start and end the transaction after enough time passes.
|
||||
* If MANUAL, TransactionId must also be set and the changes will only be applied if that transaction is still active.
|
||||
*/
|
||||
UPROPERTY()
|
||||
bool GenerateTransaction = false;
|
||||
ERCTransactionMode TransactionMode = ERCTransactionMode::NONE;
|
||||
|
||||
/**
|
||||
* The ID of the transaction with which to associate these changes. Must be provided if TransactionMode is Manual.
|
||||
*/
|
||||
UPROPERTY()
|
||||
int32 TransactionId = -1;
|
||||
|
||||
/**
|
||||
* If true, ignore the other parameters and just reset the property to its default value.
|
||||
@@ -715,7 +724,6 @@ struct FRCWebSocketPresetSetPropertyBody : public FRCRequest
|
||||
int64 SequenceNumber = -1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Holds a request made via websocket to call an exposed function on an object.
|
||||
*/
|
||||
@@ -725,10 +733,26 @@ struct FRCWebSocketCallBody : public FRCCallRequest
|
||||
GENERATED_BODY()
|
||||
|
||||
FRCWebSocketCallBody()
|
||||
: FRCCallRequest()
|
||||
: FRCCallRequest()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* How to handle generating transactions for this property change.
|
||||
* If NONE, don't generate a transaction immediately.
|
||||
* If AUTOMATIC, let the Remote Control system automatically start and end the transaction after enough time passes.
|
||||
* If MANUAL, TransactionId must also be set and the changes will only be applied if that transaction is still active.
|
||||
* If bGenerateTransaction is true, this value will be treated as if it was AUTOMATIC.
|
||||
*/
|
||||
UPROPERTY()
|
||||
ERCTransactionMode TransactionMode = ERCTransactionMode::NONE;
|
||||
|
||||
/**
|
||||
* The ID of the transaction with which to associate these changes. Must be provided if TransactionMode is Manual.
|
||||
*/
|
||||
UPROPERTY()
|
||||
int32 TransactionId = -1;
|
||||
|
||||
/**
|
||||
* The sequence number of this change. The highest sequence number received from this client will be
|
||||
* sent back to the client in future PresetFieldsChanged events.
|
||||
@@ -736,3 +760,60 @@ struct FRCWebSocketCallBody : public FRCCallRequest
|
||||
UPROPERTY()
|
||||
int64 SequenceNumber = -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Holds a request made via websocket to start a transaction.
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FRCWebSocketTransactionStartBody : public FRCRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FRCWebSocketTransactionStartBody()
|
||||
{
|
||||
AddStructParameter(ParametersFieldLabel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label for the property value struct.
|
||||
*/
|
||||
static FString ParametersFieldLabel() { return TEXT("Parameters"); }
|
||||
|
||||
/**
|
||||
* The description of the transaction.
|
||||
*/
|
||||
UPROPERTY()
|
||||
FString Description;
|
||||
|
||||
/**
|
||||
* The ID that will be used to refer to the transaction in future messages.
|
||||
*/
|
||||
UPROPERTY()
|
||||
int32 TransactionId;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Holds a request made via websocket to end a transaction.
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FRCWebSocketTransactionEndBody : public FRCRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FRCWebSocketTransactionEndBody()
|
||||
{
|
||||
AddStructParameter(ParametersFieldLabel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label for the property value struct.
|
||||
*/
|
||||
static FString ParametersFieldLabel() { return TEXT("Parameters"); }
|
||||
|
||||
/**
|
||||
* The ID of the transaction. If this doesn't match the current editor transaction, it won't be ended.
|
||||
*/
|
||||
UPROPERTY()
|
||||
int32 TransactionId;
|
||||
};
|
||||
|
||||
@@ -444,3 +444,36 @@ struct FRCActorsChangedEvent
|
||||
UPROPERTY()
|
||||
TMap<FString, FRCActorsChangedData> Changes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Event sent to a client that contributed to a transaction, indicating that the transaction was either cancelled or finalized.
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FRCTransactionEndedEvent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FRCTransactionEndedEvent()
|
||||
: Type(TEXT("TransactionEnded"))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of the event.
|
||||
*/
|
||||
UPROPERTY()
|
||||
FString Type;
|
||||
|
||||
/**
|
||||
* The client-specific ID of the transaction that was ended.
|
||||
*/
|
||||
UPROPERTY()
|
||||
int32 TransactionId;
|
||||
|
||||
/**
|
||||
* The highest sequence number received from the receiving client at the time that the transaction ended.
|
||||
*/
|
||||
UPROPERTY()
|
||||
int64 SequenceNumber = -1;
|
||||
};
|
||||
|
||||
|
||||
@@ -969,6 +969,7 @@ bool FWebRemoteControlModule::HandleObjectPropertyRoute(const FHttpServerRequest
|
||||
break;
|
||||
case ERCAccess::WRITE_ACCESS:
|
||||
case ERCAccess::WRITE_TRANSACTION_ACCESS:
|
||||
case ERCAccess::WRITE_MANUAL_TRANSACTION_ACCESS:
|
||||
{
|
||||
const FBlockDelimiters& PropertyValueDelimiters = DeserializedRequest.GetStructParameters().FindChecked(FRCObjectRequest::PropertyValueLabel());
|
||||
if (bResetToDefault)
|
||||
@@ -1090,7 +1091,7 @@ bool FWebRemoteControlModule::HandlePresetCallFunctionRoute(const FHttpServerReq
|
||||
|
||||
FRCCall Call;
|
||||
Call.CallRef = MoveTemp(CallRef);
|
||||
Call.bGenerateTransaction = CallRequest.GenerateTransaction;
|
||||
Call.TransactionMode = CallRequest.GenerateTransaction ? ERCTransactionMode::AUTOMATIC : ERCTransactionMode::NONE;
|
||||
Call.ParamStruct = FStructOnScope(FunctionArgs.GetStruct(), FunctionArgs.GetStructMemory());
|
||||
|
||||
// Invoke call with replication payload
|
||||
@@ -1179,7 +1180,8 @@ bool FWebRemoteControlModule::HandlePresetSetPropertyRoute(const FHttpServerRequ
|
||||
}
|
||||
|
||||
const bool bSuccess = WebRemoteControlInternalUtils::ModifyPropertyUsingPayload(
|
||||
*RemoteControlProperty.Get(), SetPropertyRequest, SetPropertyRequest.TCHARBody, ActingClientId, *WebSocketHandler);
|
||||
*RemoteControlProperty.Get(), SetPropertyRequest, SetPropertyRequest.TCHARBody, ActingClientId, *WebSocketHandler,
|
||||
SetPropertyRequest.GenerateTransaction ? ERCAccess::WRITE_TRANSACTION_ACCESS : ERCAccess::WRITE_ACCESS);
|
||||
|
||||
if (bSuccess)
|
||||
{
|
||||
|
||||
@@ -138,7 +138,7 @@ bool DeserializeCall(const FHttpServerRequest& InRequest, FRCCall& OutCall, cons
|
||||
if (IRemoteControlModule::Get().ResolveCall(CallRequest.ObjectPath, CallRequest.FunctionName, OutCall.CallRef, &ErrorText))
|
||||
{
|
||||
// Initialize the param struct with default parameters
|
||||
OutCall.bGenerateTransaction = CallRequest.GenerateTransaction;
|
||||
OutCall.TransactionMode = CallRequest.GenerateTransaction ? ERCTransactionMode::AUTOMATIC : ERCTransactionMode::NONE;
|
||||
OutCall.ParamStruct = FStructOnScope(OutCall.CallRef.Function.Get());
|
||||
|
||||
// If some parameters were provided, deserialize them
|
||||
|
||||
@@ -361,9 +361,10 @@ namespace WebRemoteControlInternalUtils
|
||||
* @param Payload The payload from which to deserialize property data.
|
||||
* @param ClientId The ID of the client that sent this request.
|
||||
* @param WebSocketHandler The WebSocket handler that will be notified of this remote change.
|
||||
* @param Access The access mode to use for this operation.
|
||||
*/
|
||||
template <typename RequestType>
|
||||
bool ModifyPropertyUsingPayload(FRemoteControlProperty& Property, const RequestType& Request, const TArrayView<uint8>& Payload, const FGuid& ClientId, FWebSocketMessageHandler& WebSocketHandler)
|
||||
bool ModifyPropertyUsingPayload(FRemoteControlProperty& Property, const RequestType& Request, const TArrayView<uint8>& Payload, const FGuid& ClientId, FWebSocketMessageHandler& WebSocketHandler, ERCAccess Access)
|
||||
{
|
||||
FRCObjectReference ObjectRef;
|
||||
|
||||
@@ -376,7 +377,7 @@ namespace WebRemoteControlInternalUtils
|
||||
FRCJsonStructDeserializerBackend Backend(NewPayloadReader);
|
||||
|
||||
ObjectRef.Property = Property.GetProperty();
|
||||
ObjectRef.Access = Request.GenerateTransaction ? ERCAccess::WRITE_TRANSACTION_ACCESS : ERCAccess::WRITE_ACCESS;
|
||||
ObjectRef.Access = Access;
|
||||
|
||||
bool bSuccess = true;
|
||||
|
||||
@@ -397,6 +398,16 @@ namespace WebRemoteControlInternalUtils
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
if (Access == ERCAccess::WRITE_MANUAL_TRANSACTION_ACCESS && ObjectRef.Object.IsValid())
|
||||
{
|
||||
// This transaction is being manually controlled, so RemoteControlModule's automatic transaction handling won't call this for us
|
||||
FEditPropertyChain PreEditChain;
|
||||
ObjectRef.PropertyPathInfo.ToEditPropertyChain(PreEditChain);
|
||||
ObjectRef.Object->PreEditChange(PreEditChain);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (Request.ResetToDefault)
|
||||
{
|
||||
// set interception flag as an extra argument {}
|
||||
@@ -409,6 +420,16 @@ namespace WebRemoteControlInternalUtils
|
||||
// Set a ERCPayloadType and TCHARBody in order to follow the replication path
|
||||
bSuccess &= IRemoteControlModule::Get().SetObjectProperties(ObjectRef, Backend, ERCPayloadType::Json, NewPayload, Request.Operation);
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
if (Access == ERCAccess::WRITE_MANUAL_TRANSACTION_ACCESS && ObjectRef.Object.IsValid())
|
||||
{
|
||||
// This transaction is being manually controlled, so RemoteControlModule's automatic transaction handling won't call this for us
|
||||
FPropertyChangedEvent PropertyEvent = ObjectRef.PropertyPathInfo.ToPropertyChangedEvent();
|
||||
PropertyEvent.ChangeType = EPropertyChangeType::Interactive;
|
||||
ObjectRef.Object->PostEditChangeProperty(PropertyEvent);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
|
||||
@@ -18,10 +18,15 @@
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Editor.h"
|
||||
#include "Editor/TransBuffer.h"
|
||||
#endif
|
||||
|
||||
#define LOCTEXT_NAMESPACE "WebRemoteControl"
|
||||
|
||||
static TAutoConsoleVariable<int32> CVarWebRemoteControlFramesBetweenPropertyNotifications(TEXT("WebControl.FramesBetweenPropertyNotifications"), 5, TEXT("The number of frames between sending batches of property notifications."));
|
||||
const int64 FWebSocketMessageHandler::DefaultSequenceNumber = -1;
|
||||
const int32 FWebSocketMessageHandler::InvalidTransactionId = -1;
|
||||
const FTimespan FWebSocketMessageHandler::TransactionTimeout = FTimespan::FromSeconds(3.f);
|
||||
|
||||
namespace WebSocketMessageHandlerStructUtils
|
||||
{
|
||||
@@ -269,6 +274,14 @@ void FWebSocketMessageHandler::RegisterRoutes(FWebRemoteControlModule* WebRemote
|
||||
{
|
||||
FCoreDelegates::OnPostEngineInit.AddRaw(this, &FWebSocketMessageHandler::RegisterActorHandlers);
|
||||
}
|
||||
|
||||
if (GEditor)
|
||||
{
|
||||
if (UTransBuffer* TransBuffer = Cast<UTransBuffer>(GEditor->Trans))
|
||||
{
|
||||
TransBuffer->OnTransactionStateChanged().AddRaw(this, &FWebSocketMessageHandler::HandleTransactionStateChanged);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// WebSocket routes
|
||||
@@ -313,6 +326,18 @@ void FWebSocketMessageHandler::RegisterRoutes(FWebRemoteControlModule* WebRemote
|
||||
TEXT("object.call"),
|
||||
FWebSocketMessageDelegate::CreateRaw(this, &FWebSocketMessageHandler::HandleWebSocketFunctionCall)
|
||||
));
|
||||
|
||||
RegisterRoute(WebRemoteControl, MakeUnique<FRemoteControlWebsocketRoute>(
|
||||
TEXT("Begin a manual editor transaction. Be sure to send transaction.end when finished."),
|
||||
TEXT("transaction.begin"),
|
||||
FWebSocketMessageDelegate::CreateRaw(this, &FWebSocketMessageHandler::HandleWebSocketBeginEditorTransaction)
|
||||
));
|
||||
|
||||
RegisterRoute(WebRemoteControl, MakeUnique<FRemoteControlWebsocketRoute>(
|
||||
TEXT("End a manual editor transaction"),
|
||||
TEXT("transaction.end"),
|
||||
FWebSocketMessageDelegate::CreateRaw(this, &FWebSocketMessageHandler::HandleWebSocketEndEditorTransaction)
|
||||
));
|
||||
}
|
||||
|
||||
void FWebSocketMessageHandler::UnregisterRoutes(FWebRemoteControlModule* WebRemoteControl)
|
||||
@@ -331,6 +356,14 @@ void FWebSocketMessageHandler::UnregisterRoutes(FWebRemoteControlModule* WebRemo
|
||||
GEngine->OnLevelActorListChanged().Remove(OnActorListChangedHandle);
|
||||
GEngine->OnLevelActorListChanged().Remove(OnWorldDestroyedHandle);
|
||||
}
|
||||
|
||||
if (GEditor)
|
||||
{
|
||||
if (UTransBuffer* TransBuffer = Cast<UTransBuffer>(GEditor->Trans))
|
||||
{
|
||||
TransBuffer->OnTransactionStateChanged().RemoveAll(this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for (const TUniquePtr<FRemoteControlWebsocketRoute>& Route : Routes)
|
||||
@@ -580,6 +613,33 @@ void FWebSocketMessageHandler::HandleWebSocketPresetModifyProperty(const FRemote
|
||||
return;
|
||||
}
|
||||
|
||||
ERCAccess Access;
|
||||
switch (Body.TransactionMode)
|
||||
{
|
||||
case ERCTransactionMode::NONE:
|
||||
Access = ERCAccess::WRITE_ACCESS;
|
||||
break;
|
||||
|
||||
case ERCTransactionMode::AUTOMATIC:
|
||||
Access = ERCAccess::WRITE_TRANSACTION_ACCESS;
|
||||
break;
|
||||
|
||||
case ERCTransactionMode::MANUAL:
|
||||
Access = ERCAccess::WRITE_MANUAL_TRANSACTION_ACCESS;
|
||||
|
||||
// Indicate that we want to contribute to this transaction if it's active
|
||||
if (!ContributeToTransaction(WebSocketMessage.ClientId, Body.TransactionId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
UE_LOG(LogRemoteControl, Warning, TEXT("Unknown transaction mode %d"), Body.TransactionMode);
|
||||
return;
|
||||
}
|
||||
|
||||
URemoteControlPreset* Preset = IRemoteControlModule::Get().ResolvePreset(Body.PresetName);
|
||||
if (Preset == nullptr)
|
||||
{
|
||||
@@ -596,7 +656,7 @@ void FWebSocketMessageHandler::HandleWebSocketPresetModifyProperty(const FRemote
|
||||
|
||||
UpdateSequenceNumber(WebSocketMessage.ClientId, Body.SequenceNumber);
|
||||
|
||||
WebRemoteControlInternalUtils::ModifyPropertyUsingPayload(*RemoteControlProperty.Get(), Body, WebSocketMessage.RequestPayload, WebSocketMessage.ClientId, *this);
|
||||
WebRemoteControlInternalUtils::ModifyPropertyUsingPayload(*RemoteControlProperty.Get(), Body, WebSocketMessage.RequestPayload, WebSocketMessage.ClientId, *this, Access);
|
||||
}
|
||||
|
||||
void FWebSocketMessageHandler::HandleWebSocketFunctionCall(const FRemoteControlWebSocketMessage& WebSocketMessage)
|
||||
@@ -607,11 +667,16 @@ void FWebSocketMessageHandler::HandleWebSocketFunctionCall(const FRemoteControlW
|
||||
return;
|
||||
}
|
||||
|
||||
if (Body.GenerateTransaction)
|
||||
{
|
||||
Body.TransactionMode = ERCTransactionMode::AUTOMATIC;
|
||||
}
|
||||
|
||||
FRCCall Call;
|
||||
if (IRemoteControlModule::Get().ResolveCall(Body.ObjectPath, Body.FunctionName, Call.CallRef, nullptr))
|
||||
{
|
||||
// Initialize the param struct with default parameters
|
||||
Call.bGenerateTransaction = Body.GenerateTransaction;
|
||||
Call.TransactionMode = Body.GenerateTransaction ? ERCTransactionMode::AUTOMATIC : Body.TransactionMode;
|
||||
Call.ParamStruct = FStructOnScope(Call.CallRef.Function.Get());
|
||||
|
||||
// If some parameters were provided, deserialize them
|
||||
@@ -644,11 +709,66 @@ void FWebSocketMessageHandler::HandleWebSocketFunctionCall(const FRemoteControlW
|
||||
return;
|
||||
}
|
||||
|
||||
if (Body.TransactionMode == ERCTransactionMode::MANUAL)
|
||||
{
|
||||
// Indicate that we want to contribute to this transaction
|
||||
if (!ContributeToTransaction(WebSocketMessage.ClientId, Body.TransactionId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateSequenceNumber(WebSocketMessage.ClientId, Body.SequenceNumber);
|
||||
|
||||
IRemoteControlModule::Get().InvokeCall(Call);
|
||||
}
|
||||
|
||||
void FWebSocketMessageHandler::HandleWebSocketBeginEditorTransaction(const FRemoteControlWebSocketMessage& WebSocketMessage)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
FRCWebSocketTransactionStartBody Body;
|
||||
if (!WebRemoteControlInternalUtils::DeserializeRequestPayload(WebSocketMessage.RequestPayload, nullptr, Body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FText Description = FText::Format(LOCTEXT("RemoteControlTransaction", "Remote Control - {0}"), FText::FromString(Body.Description));
|
||||
const FGuid TransactionGuid = IRemoteControlModule::Get().BeginManualEditorTransaction(Description, 0);
|
||||
|
||||
if (TransactionGuid.IsValid())
|
||||
{
|
||||
ClientsByTransactionGuid.FindOrAdd(TransactionGuid);
|
||||
ContributeToTransaction(WebSocketMessage.ClientId, Body.TransactionId);
|
||||
|
||||
TransactionIdsByClientId.FindOrAdd(WebSocketMessage.ClientId).Add({ TransactionGuid, Body.TransactionId });
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send a message indicating that the transaction ended immediately so the client knows it wasn't created
|
||||
FRCTransactionEndedEvent Event;
|
||||
Event.TransactionId = Body.TransactionId;
|
||||
Event.SequenceNumber = GetSequenceNumber(WebSocketMessage.ClientId);
|
||||
|
||||
TArray<uint8> Payload;
|
||||
WebRemoteControlUtils::SerializeMessage(Event, Payload);
|
||||
Server->Send(WebSocketMessage.ClientId, Payload);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void FWebSocketMessageHandler::HandleWebSocketEndEditorTransaction(const FRemoteControlWebSocketMessage& WebSocketMessage)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
FRCWebSocketTransactionEndBody Body;
|
||||
if (!WebRemoteControlInternalUtils::DeserializeRequestPayload(WebSocketMessage.RequestPayload, nullptr, Body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EndClientTransaction(WebSocketMessage.ClientId, Body.TransactionId);
|
||||
#endif
|
||||
}
|
||||
|
||||
void FWebSocketMessageHandler::ProcessChangedProperties()
|
||||
{
|
||||
//Go over each property that were changed for each preset
|
||||
@@ -681,8 +801,7 @@ void FWebSocketMessageHandler::ProcessChangedProperties()
|
||||
// Send a property change event for each property type
|
||||
for (const TPair<uint64, TSet<FGuid>>& ClassToEventsPair : PropertyIdsByType)
|
||||
{
|
||||
const int64* ClientSequenceNumber = ClientSequenceNumbers.Find(ClientToEventsPair.Key);
|
||||
const int64 SequenceNumber = ClientSequenceNumber ? *ClientSequenceNumber : DefaultSequenceNumber;
|
||||
const int64 SequenceNumber = GetSequenceNumber(ClientToEventsPair.Key);
|
||||
|
||||
TArray<uint8> WorkingBuffer;
|
||||
if (ClientToEventsPair.Value.Num() && WritePropertyChangeEventPayload(Preset, { ClassToEventsPair.Value }, SequenceNumber, WorkingBuffer))
|
||||
@@ -943,7 +1062,7 @@ void FWebSocketMessageHandler::OnConnectionClosedCallback(FGuid ClientId)
|
||||
TArray<FGuid>& ClientList = Iter.Value();
|
||||
|
||||
ClientList.Remove(ClientId);
|
||||
if (ClientList.Num() == 0)
|
||||
if (ClientList.IsEmpty())
|
||||
{
|
||||
RemoteControl.DestroyTransientPreset(PresetId);
|
||||
PresetNotificationMap.Remove(PresetId);
|
||||
@@ -951,6 +1070,21 @@ void FWebSocketMessageHandler::OnConnectionClosedCallback(FGuid ClientId)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the client as a listener for any active transactions
|
||||
for (auto Iter = ClientsByTransactionGuid.CreateIterator(); Iter; ++Iter)
|
||||
{
|
||||
IRemoteControlModule::Get().EndManualEditorTransaction(Iter.Key());
|
||||
|
||||
TMap<FGuid, FDateTime>& Clients = Iter.Value();
|
||||
Clients.Remove(ClientId);
|
||||
if (Clients.IsEmpty())
|
||||
{
|
||||
Iter.RemoveCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
TransactionIdsByClientId.Remove(ClientId);
|
||||
|
||||
/** Remove this client's config. */
|
||||
ClientConfigMap.Remove(ClientId);
|
||||
ClientSequenceNumbers.Remove(ClientId);
|
||||
@@ -958,16 +1092,18 @@ void FWebSocketMessageHandler::OnConnectionClosedCallback(FGuid ClientId)
|
||||
|
||||
void FWebSocketMessageHandler::OnEndFrame()
|
||||
{
|
||||
//Early exit if no clients are requesting notifications
|
||||
if (PresetNotificationMap.Num() <= 0 && ActorNotificationMap.Num() <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyNotificationFrameCounter++;
|
||||
|
||||
if (PropertyNotificationFrameCounter >= CVarWebRemoteControlFramesBetweenPropertyNotifications.GetValueOnGameThread())
|
||||
{
|
||||
TimeOutTransactions();
|
||||
|
||||
//Early exit if no clients are requesting notifications
|
||||
if (PresetNotificationMap.Num() <= 0 && ActorNotificationMap.Num() <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyNotificationFrameCounter = 0;
|
||||
ProcessChangedProperties();
|
||||
ProcessChangedActorProperties();
|
||||
@@ -980,6 +1116,30 @@ void FWebSocketMessageHandler::OnEndFrame()
|
||||
}
|
||||
}
|
||||
|
||||
void FWebSocketMessageHandler::TimeOutTransactions()
|
||||
{
|
||||
TArray<TPair<FGuid, int32>> TransactionsToEnd;
|
||||
const FDateTime Now = FDateTime::Now();
|
||||
|
||||
for (const TPair<FGuid, TMap<FGuid, FDateTime>>& TransactionClientsPair : ClientsByTransactionGuid)
|
||||
{
|
||||
for (const TPair<FGuid, FDateTime>& ClientTimePair : TransactionClientsPair.Value)
|
||||
{
|
||||
if (Now >= ClientTimePair.Value + TransactionTimeout)
|
||||
{
|
||||
// This transaction has timed out; we should force it to end
|
||||
TransactionsToEnd.Add({ ClientTimePair.Key, GetClientTransactionId(ClientTimePair.Key, TransactionClientsPair.Key) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do this as a separate step since it may remove entries from the map during iteration
|
||||
for (auto TransactionIterator : TransactionsToEnd)
|
||||
{
|
||||
EndClientTransaction(TransactionIterator.Key, TransactionIterator.Value);
|
||||
}
|
||||
}
|
||||
|
||||
void FWebSocketMessageHandler::ProcessAddedProperties()
|
||||
{
|
||||
for (const TPair<FGuid, TArray<FGuid>>& Entry : PerFrameAddedProperties)
|
||||
@@ -1591,6 +1751,45 @@ void FWebSocketMessageHandler::OnObjectTransacted(UObject* Object, const class F
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void FWebSocketMessageHandler::HandleTransactionStateChanged(const FTransactionContext& InTransactionContext, const ETransactionStateEventType InTransactionState)
|
||||
{
|
||||
if (InTransactionState == ETransactionStateEventType::TransactionCanceled || InTransactionState == ETransactionStateEventType::TransactionFinalized)
|
||||
{
|
||||
HandleTransactionEnded(InTransactionContext.TransactionId);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void FWebSocketMessageHandler::HandleTransactionEnded(const FGuid& TransactionGuid)
|
||||
{
|
||||
if (const TMap<FGuid, FDateTime>* TransactionClients = ClientsByTransactionGuid.Find(TransactionGuid))
|
||||
{
|
||||
FRCTransactionEndedEvent Event;
|
||||
|
||||
// Notify the clients
|
||||
for (const TPair<FGuid, FDateTime>& ClientTimePair : *TransactionClients)
|
||||
{
|
||||
const FGuid& ClientId = ClientTimePair.Key;
|
||||
|
||||
Event.TransactionId = GetClientTransactionId(ClientId, TransactionGuid);
|
||||
Event.SequenceNumber = GetSequenceNumber(ClientId);
|
||||
|
||||
TArray<uint8> Payload;
|
||||
WebRemoteControlUtils::SerializeMessage(Event, Payload);
|
||||
Server->Send(ClientId, Payload);
|
||||
}
|
||||
|
||||
// Forget about the transaction
|
||||
ClientsByTransactionGuid.Remove(TransactionGuid);
|
||||
|
||||
for (TPair<FGuid, TMap<FGuid, int32>>& ClientIdsPair : TransactionIdsByClientId)
|
||||
{
|
||||
ClientIdsPair.Value.Remove(TransactionGuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FWebSocketMessageHandler::StartWatchingActor(AActor* Actor, UClass* WatchedClass)
|
||||
{
|
||||
FWatchedActorData* ActorData = WatchedActors.Find(Actor);
|
||||
@@ -1676,3 +1875,87 @@ void FWebSocketMessageHandler::UpdateSequenceNumber(const FGuid& ClientId, int64
|
||||
StoredSequenceNumber = NewSequenceNumber;
|
||||
}
|
||||
}
|
||||
|
||||
bool FWebSocketMessageHandler::ContributeToTransaction(const FGuid& ClientId, int32 TransactionId)
|
||||
{
|
||||
const FGuid InternalId = GetTransactionGuid(ClientId, TransactionId);
|
||||
if (ClientId.IsValid())
|
||||
{
|
||||
if (TMap<FGuid, FDateTime>* TransactionClients = ClientsByTransactionGuid.Find(InternalId))
|
||||
{
|
||||
TransactionClients->Add(ClientId, FDateTime::Now());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FWebSocketMessageHandler::EndClientTransaction(const FGuid& ClientId, int32 TransactionId)
|
||||
{
|
||||
if (TMap<FGuid, int32>* TransactionIds = TransactionIdsByClientId.Find(ClientId))
|
||||
{
|
||||
const FGuid TransactionGuid = GetTransactionGuid(ClientId, TransactionId);
|
||||
|
||||
if (!TransactionGuid.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop tracking the transaction for this client
|
||||
if (TMap<FGuid, FDateTime>* TransactionClients = ClientsByTransactionGuid.Find(TransactionGuid))
|
||||
{
|
||||
const int32 Result = IRemoteControlModule::Get().EndManualEditorTransaction(TransactionGuid);
|
||||
|
||||
if (Result != INDEX_NONE && Result <= 1)
|
||||
{
|
||||
// This was the last action, so the transaction will end. This will clean up the client and ID maps for us.
|
||||
HandleTransactionEnded(TransactionGuid);
|
||||
return;
|
||||
}
|
||||
|
||||
// Transaction still exists, but we can remove the client from its list
|
||||
TransactionClients->Remove(ClientId);
|
||||
}
|
||||
|
||||
// Do this last so we can still look up the client ID/transaction GUID mapping in HandleTransactionEnded
|
||||
TransactionIds->Remove(TransactionGuid);
|
||||
}
|
||||
}
|
||||
|
||||
int64 FWebSocketMessageHandler::GetSequenceNumber(const FGuid& ClientId) const
|
||||
{
|
||||
const int64* ClientSequenceNumber = ClientSequenceNumbers.Find(ClientId);
|
||||
return ClientSequenceNumber ? *ClientSequenceNumber : DefaultSequenceNumber;
|
||||
}
|
||||
|
||||
FGuid FWebSocketMessageHandler::GetTransactionGuid(const FGuid& ClientId, int32 TransactionId) const
|
||||
{
|
||||
if (const TMap<FGuid, int32>* TransactionIdPairs = TransactionIdsByClientId.Find(ClientId))
|
||||
{
|
||||
for (const TPair<FGuid, int32>& Pair : *TransactionIdPairs)
|
||||
{
|
||||
if (TransactionId == Pair.Value)
|
||||
{
|
||||
return Pair.Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FGuid();
|
||||
}
|
||||
|
||||
int32 FWebSocketMessageHandler::GetClientTransactionId(const FGuid& ClientId, const FGuid& TransactionGuid) const
|
||||
{
|
||||
if (const TMap<FGuid, int32>* TransactionIds = TransactionIdsByClientId.Find(ClientId))
|
||||
{
|
||||
if (const int32* ClientTransactionId = TransactionIds->Find(TransactionGuid))
|
||||
{
|
||||
return *ClientTransactionId;
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidTransactionId;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
@@ -105,6 +105,12 @@ private:
|
||||
/** Handles calling a Blueprint function */
|
||||
void HandleWebSocketFunctionCall(const FRemoteControlWebSocketMessage& WebSocketMessage);
|
||||
|
||||
/** Handles beginning a manual editor transaction. */
|
||||
void HandleWebSocketBeginEditorTransaction(const FRemoteControlWebSocketMessage& WebSocketMessage);
|
||||
|
||||
/** Handles ending a manual editor transaction. */
|
||||
void HandleWebSocketEndEditorTransaction(const FRemoteControlWebSocketMessage& WebSocketMessage);
|
||||
|
||||
//Preset callbacks
|
||||
void OnPresetExposedPropertiesModified(URemoteControlPreset* Owner, const TSet<FGuid>& ModifiedPropertyIds);
|
||||
void OnPropertyExposed(URemoteControlPreset* Owner, const FGuid& EntityId);
|
||||
@@ -121,6 +127,9 @@ private:
|
||||
/** End of frame callback to send cached property changed, preset changed messages */
|
||||
void OnEndFrame();
|
||||
|
||||
/** Check if any transactions have timed out and end them. */
|
||||
void TimeOutTransactions();
|
||||
|
||||
/** If properties have changed during the frame, send out notifications to listeners */
|
||||
void ProcessChangedProperties();
|
||||
|
||||
@@ -214,6 +223,18 @@ private:
|
||||
*/
|
||||
void OnObjectTransacted(UObject* Object, const class FTransactionObjectEvent& TransactionEvent);
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Called when the state of an editor transaction changes.
|
||||
*/
|
||||
void HandleTransactionStateChanged(const FTransactionContext& InTransactionContext, const ETransactionStateEventType InTransactionState);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Handle a transaction ending (either cancelled or finalized).
|
||||
*/
|
||||
void HandleTransactionEnded(const FGuid& TransactionGuid);
|
||||
|
||||
/**
|
||||
* Start watching an actor because it's a member of the given class.
|
||||
*/
|
||||
@@ -239,12 +260,45 @@ private:
|
||||
* Update the sequence number for a client when a new one is received.
|
||||
*/
|
||||
void UpdateSequenceNumber(const FGuid& ClientId, int64 NewSequenceNumber);
|
||||
|
||||
/**
|
||||
* Get the current sequence number for a client.
|
||||
*/
|
||||
int64 GetSequenceNumber(const FGuid& ClientId) const;
|
||||
|
||||
/**
|
||||
* Indicate that a client is going to contribute to the transaction with the given ID.
|
||||
* This should be called whenever a change is made that will be part of the transaction, not just at the start.
|
||||
* Returns true if the client can contribute to a transaction with that ID.
|
||||
*/
|
||||
bool ContributeToTransaction(const FGuid& ClientId, int32 TransactionId);
|
||||
|
||||
/**
|
||||
* End the transaction with the given ID for the client with the given ID.
|
||||
*/
|
||||
void EndClientTransaction(const FGuid& ClientId, int32 TransactionId);
|
||||
|
||||
/**
|
||||
* Converts a client's transaction ID to its editor internal GUID.
|
||||
*/
|
||||
FGuid GetTransactionGuid(const FGuid& ClientId, int32 TransactionId) const;
|
||||
|
||||
/**
|
||||
* Converts an internal transaction GUID to the ID used by the given client to refer to it.
|
||||
*/
|
||||
int32 GetClientTransactionId(const FGuid& ClientId, const FGuid& TransactionGuid) const;
|
||||
|
||||
private:
|
||||
|
||||
/** Default sequence number for a client that hasn't reported one yet. */
|
||||
static const int64 DefaultSequenceNumber;
|
||||
|
||||
/** Client transaction ID for a transaction that doesn't exist. */
|
||||
static const int32 InvalidTransactionId;
|
||||
|
||||
/** When this much time has passed since a client last contributed to a transaction, the transaction will automatically end. */
|
||||
static const FTimespan TransactionTimeout;
|
||||
|
||||
/** Map type from class to Guids of clients listening for changes to actors of that class. */
|
||||
typedef TMap<TWeakObjectPtr<UClass>, FWatchedClassData, FDefaultSetAllocator, TWeakObjectPtrMapKeyFuncs<TWeakObjectPtr<UClass>, FWatchedClassData>> FActorNotificationMap;
|
||||
|
||||
@@ -316,6 +370,12 @@ private:
|
||||
|
||||
/** Map from transient preset ID to clients which, when all disconnected, will automatically destroy the preset. */
|
||||
TMap<FGuid, TArray<FGuid>> TransientPresetAutoDestroyClients;
|
||||
|
||||
/** Map from transasction ID to a map of (contributing client ID, time of last contribution to the transaction). */
|
||||
TMap<FGuid, TMap<FGuid, FDateTime>> ClientsByTransactionGuid;
|
||||
|
||||
/** Map from client ID to pairs of editor GUID/client ID used to refer to the transaction. */
|
||||
TMap<FGuid, TMap<FGuid, int32>> TransactionIdsByClientId;
|
||||
|
||||
/** Holds the ID of the client currently making a request. Used to prevent sending back notifications to it. */
|
||||
const FGuid& ActingClientId;
|
||||
|
||||
Reference in New Issue
Block a user