350 lines
7.5 KiB
C++
350 lines
7.5 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Trace/Config.h"
|
|
|
|
#include "Trace/Platform.h"
|
|
#include "Trace/Message.h"
|
|
#include "Trace/Detail/Channel.h"
|
|
|
|
#include "Misc/CString.h"
|
|
#include "Templates/UnrealTemplate.h"
|
|
|
|
#include <type_traits>
|
|
|
|
namespace UE {
|
|
namespace Trace {
|
|
namespace Private {
|
|
|
|
#if TRACE_PRIVATE_ALLOW_TCP_CONTROL
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
bool Writer_SendTo(const ANSICHAR*, uint32=0, uint32=0);
|
|
bool Writer_WriteTo(const ANSICHAR*, uint32=0);
|
|
bool Writer_Stop();
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
enum class EControlState : uint8
|
|
{
|
|
Closed = 0,
|
|
Listening,
|
|
Accepted,
|
|
Failed,
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
struct FControlCommands
|
|
{
|
|
enum { Max = 8 };
|
|
struct
|
|
{
|
|
uint32 Hash;
|
|
void* Param;
|
|
void (*Thunk)(void*, uint32, ANSICHAR const* const*);
|
|
} Commands[Max];
|
|
uint8 Count;
|
|
};
|
|
static_assert(std::is_trivial<FControlCommands>(), "FControlCommands must be trivial");
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
static FControlCommands GControlCommands;
|
|
static UPTRINT GControlListen = 0;
|
|
static UPTRINT GControlSocket = 0;
|
|
static EControlState GControlState; // = EControlState::Closed;
|
|
static uint16 GControlPort = 1985;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
static uint32 Writer_ControlHash(const ANSICHAR* Word)
|
|
{
|
|
uint32 Hash = 5381;
|
|
for (; *Word; (Hash = (Hash * 33) ^ *Word), ++Word);
|
|
return Hash;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
static bool Writer_ControlAddCommand(
|
|
const ANSICHAR* Name,
|
|
void* Param,
|
|
void (*Thunk)(void*, uint32, ANSICHAR const* const*))
|
|
{
|
|
if (GControlCommands.Count >= FControlCommands::Max)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint32 Index = GControlCommands.Count++;
|
|
GControlCommands.Commands[Index] = { Writer_ControlHash(Name), Param, Thunk };
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
static bool Writer_ControlDispatch(uint32 ArgC, ANSICHAR const* const* ArgV)
|
|
{
|
|
if (ArgC == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint32 Hash = Writer_ControlHash(ArgV[0]);
|
|
--ArgC;
|
|
++ArgV;
|
|
|
|
for (int i = 0, n = GControlCommands.Count; i < n; ++i)
|
|
{
|
|
const auto& Command = GControlCommands.Commands[i];
|
|
if (Command.Hash == Hash)
|
|
{
|
|
Command.Thunk(Command.Param, ArgC, ArgV);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
static bool Writer_ControlListen()
|
|
{
|
|
GControlListen = TcpSocketListen(GControlPort);
|
|
if (!GControlListen)
|
|
{
|
|
uint32 Seed = uint32(TimeGetTimestamp());
|
|
for (uint32 i = 0; i < 10 && !GControlListen; Seed *= 13, ++i)
|
|
{
|
|
uint16 Port((Seed & 0x1fff) + 0x8000);
|
|
GControlListen = TcpSocketListen(Port);
|
|
if (GControlListen)
|
|
{
|
|
GControlPort = Port;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!GControlListen)
|
|
{
|
|
//This unfortunately triggers on editor shutdown needlessly spamming the log
|
|
//UE_TRACE_ERRORMESSAGE_F(ListenFail, GetLastErrorCode(), "Port: %d", GControlPort);
|
|
GControlState = EControlState::Failed;
|
|
return false;
|
|
}
|
|
|
|
UE_TRACE_MESSAGE_F(Display, "Control listening on port %u", GControlPort);
|
|
GControlState = EControlState::Listening;
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
static bool Writer_ControlAccept()
|
|
{
|
|
UPTRINT Socket;
|
|
int Return = TcpSocketAccept(GControlListen, Socket);
|
|
if (Return <= 0)
|
|
{
|
|
if (Return == -1)
|
|
{
|
|
IoClose(GControlListen);
|
|
GControlListen = 0;
|
|
GControlState = EControlState::Failed;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
GControlState = EControlState::Accepted;
|
|
GControlSocket = Socket;
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
static void Writer_ControlRecv()
|
|
{
|
|
// We'll assume that commands are smaller than the canonical MTU so this
|
|
// doesn't need to be implemented in a reentrant manner (maybe).
|
|
|
|
ANSICHAR Buffer[512];
|
|
ANSICHAR* __restrict Head = Buffer;
|
|
while (TcpSocketHasData(GControlSocket))
|
|
{
|
|
int32 ReadSize = int32(UPTRINT(Buffer + sizeof(Buffer) - Head));
|
|
int32 Recvd = IoRead(GControlSocket, Head, ReadSize);
|
|
if (Recvd <= 0)
|
|
{
|
|
IoClose(GControlSocket);
|
|
GControlSocket = 0;
|
|
GControlState = EControlState::Listening;
|
|
break;
|
|
}
|
|
|
|
Head += Recvd;
|
|
|
|
enum EParseState
|
|
{
|
|
CrLfSkip,
|
|
WhitespaceSkip,
|
|
Word,
|
|
} ParseState = EParseState::CrLfSkip;
|
|
|
|
uint32 ArgC = 0;
|
|
const ANSICHAR* ArgV[16];
|
|
|
|
const ANSICHAR* __restrict Spent = Buffer;
|
|
for (ANSICHAR* __restrict Cursor = Buffer; Cursor < Head; ++Cursor)
|
|
{
|
|
switch (ParseState)
|
|
{
|
|
case EParseState::CrLfSkip:
|
|
if (*Cursor == '\n' || *Cursor == '\r')
|
|
{
|
|
continue;
|
|
}
|
|
ParseState = EParseState::WhitespaceSkip;
|
|
/* [[fallthrough]] */
|
|
|
|
case EParseState::WhitespaceSkip:
|
|
if (*Cursor == ' ' || *Cursor == '\0')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ArgC < UE_ARRAY_COUNT(ArgV))
|
|
{
|
|
ArgV[ArgC] = Cursor;
|
|
++ArgC;
|
|
}
|
|
|
|
ParseState = EParseState::Word;
|
|
/* [[fallthrough]] */
|
|
|
|
case EParseState::Word:
|
|
if (*Cursor == ' ' || *Cursor == '\0')
|
|
{
|
|
*Cursor = '\0';
|
|
ParseState = EParseState::WhitespaceSkip;
|
|
continue;
|
|
}
|
|
|
|
if (*Cursor == '\r' || *Cursor == '\n')
|
|
{
|
|
*Cursor = '\0';
|
|
|
|
Writer_ControlDispatch(ArgC, ArgV);
|
|
|
|
ArgC = 0;
|
|
Spent = Cursor + 1;
|
|
ParseState = EParseState::CrLfSkip;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
int32 UnspentSize = int32(UPTRINT(Head - Spent));
|
|
if (UnspentSize)
|
|
{
|
|
memmove(Buffer, Spent, UnspentSize);
|
|
}
|
|
Head = Buffer + UnspentSize;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
uint32 Writer_GetControlPort()
|
|
{
|
|
return GControlPort;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Writer_UpdateControl()
|
|
{
|
|
switch (GControlState)
|
|
{
|
|
case EControlState::Closed:
|
|
if (!Writer_ControlListen())
|
|
{
|
|
break;
|
|
}
|
|
/* [[fallthrough]] */
|
|
|
|
case EControlState::Listening:
|
|
if (!Writer_ControlAccept())
|
|
{
|
|
break;
|
|
}
|
|
/* [[fallthrough]] */
|
|
|
|
case EControlState::Accepted:
|
|
Writer_ControlRecv();
|
|
break;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Writer_InitializeControl()
|
|
{
|
|
Writer_ControlAddCommand("SendTo", nullptr,
|
|
[] (void*, uint32 ArgC, ANSICHAR const* const* ArgV)
|
|
{
|
|
if (ArgC > 0)
|
|
{
|
|
Writer_SendTo(ArgV[0]);
|
|
}
|
|
}
|
|
);
|
|
|
|
Writer_ControlAddCommand("Stop", nullptr,
|
|
[] (void*, uint32 ArgC, ANSICHAR const* const* ArgV)
|
|
{
|
|
Writer_Stop();
|
|
}
|
|
);
|
|
|
|
Writer_ControlAddCommand("ToggleChannels", nullptr,
|
|
[] (void*, uint32 ArgC, ANSICHAR const* const* ArgV)
|
|
{
|
|
if (ArgC < 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const size_t BufferSize = 512;
|
|
ANSICHAR Channels[BufferSize] = {};
|
|
ANSICHAR* Ctx;
|
|
const bool bState = (ArgV[1][0] != '0');
|
|
FPlatformString::Strncpy(Channels, ArgV[0], BufferSize);
|
|
ANSICHAR* Channel = FCStringAnsi::Strtok(Channels, ",", &Ctx);
|
|
while (Channel)
|
|
{
|
|
FChannel::Toggle(Channel, bState);
|
|
Channel = FCStringAnsi::Strtok(nullptr, ",", &Ctx);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void Writer_ShutdownControl()
|
|
{
|
|
if (GControlListen)
|
|
{
|
|
IoClose(GControlListen);
|
|
GControlListen = 0;
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
void Writer_InitializeControl() {}
|
|
void Writer_ShutdownControl() {}
|
|
void Writer_UpdateControl() {}
|
|
uint32 Writer_GetControlPort() { return ~0u; }
|
|
|
|
#endif // TRACE_PRIVATE_ALLOW_TCP_CONTROL
|
|
|
|
} // namespace Private
|
|
} // namespace Trace
|
|
} // namespace UE
|