Progress on Animation Queue Pipeline
This commit is contained in:
Binary file not shown.
BIN
Content/TanActor.uasset
LFS
BIN
Content/TanActor.uasset
LFS
Binary file not shown.
Binary file not shown.
@@ -83,3 +83,147 @@ FString FAnimStepDecoder::DebugString(const FAnimStep& step) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//// Our own copy of the animation queue. We only
|
||||||
|
//// store the hashes, not the steps. The First element
|
||||||
|
//// of the queue is the oldest item.
|
||||||
|
////
|
||||||
|
//TDeque<FAnimStoredStep> AQ;
|
||||||
|
|
||||||
|
//// The sequence number of the first item in AQ.
|
||||||
|
////
|
||||||
|
//int32 FirstSeqno;
|
||||||
|
|
||||||
|
//// Map from hash to sequence number.
|
||||||
|
////
|
||||||
|
//TMap<uint64, int32> HashToSeqno;
|
||||||
|
|
||||||
|
//// The sequence number of the first unstarted animation.
|
||||||
|
////
|
||||||
|
//int32 UnstartedSeqno;
|
||||||
|
|
||||||
|
//// Array of recently-aborted hash values.
|
||||||
|
//TArray<uint64> AbortedHashes;
|
||||||
|
|
||||||
|
|
||||||
|
FAnimTracker::FAnimTracker() {
|
||||||
|
AQ.Empty();
|
||||||
|
FirstSeqno = 0;
|
||||||
|
HashToSeqno.Empty();
|
||||||
|
UnstartedSeqno = 0;
|
||||||
|
AbortedHashes.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FAnimTracker::Update(std::string_view encqueue) {
|
||||||
|
// Check for an exact match. If the most recent entry
|
||||||
|
// in encqueue has the same hash as the most recent
|
||||||
|
// entry in AQ, then that's an exact match. Or,
|
||||||
|
// if both are empty, that's also an exact match.
|
||||||
|
// In case of exact match, abort early, there's no
|
||||||
|
// further work needed.
|
||||||
|
//
|
||||||
|
if (encqueue.empty()) {
|
||||||
|
if (AQ.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!AQ.IsEmpty()) {
|
||||||
|
FStringDecoder qdecoder(encqueue);
|
||||||
|
uint64 hash = qdecoder.read_uint64();
|
||||||
|
if (hash == AQ.Last().Hash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for a Luprex animation record that matches
|
||||||
|
// something that we already have. Yields the sequence
|
||||||
|
// number of the most recent matching record.
|
||||||
|
//
|
||||||
|
// At the same time, push all non-matching records
|
||||||
|
// after the matching record onto a stack of new records.
|
||||||
|
//
|
||||||
|
FAnimQueueDecoder decoder(encqueue);
|
||||||
|
TArray<FAnimStep> newsteps;
|
||||||
|
int32 matchingseqno = -1;
|
||||||
|
while (!decoder.AtEOF()) {
|
||||||
|
FAnimStep step = decoder.ReadStep();
|
||||||
|
int32* stepseq = HashToSeqno.Find(step.Hash);
|
||||||
|
if (stepseq == nullptr) {
|
||||||
|
newsteps.Emplace(step);
|
||||||
|
} else {
|
||||||
|
matchingseqno = *stepseq;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort all animations after the most recent matching
|
||||||
|
// record. Animations are aborted in most-recent-first order.
|
||||||
|
//
|
||||||
|
int32 nabort = (FirstSeqno + AQ.Num()) - (matchingseqno + 1);
|
||||||
|
check((nabort >= 0) && (nabort <= AQ.Num()));
|
||||||
|
for (int32 i = 0; i < nabort; i++) {
|
||||||
|
uint64 lasthash = AQ.Last().Hash;
|
||||||
|
HashToSeqno.Remove(lasthash);
|
||||||
|
AbortedHashes.Emplace(lasthash);
|
||||||
|
AQ.PopLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer the new animations onto the queue.
|
||||||
|
//
|
||||||
|
while (!newsteps.IsEmpty()) {
|
||||||
|
FAnimStep step = newsteps.Pop();
|
||||||
|
int32 seqno = FirstSeqno + AQ.Num();
|
||||||
|
AQ.EmplaceLast(step.Hash, step.Body);
|
||||||
|
HashToSeqno.Emplace(step.Hash, seqno);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move back the Unstarted pointer, so that the
|
||||||
|
// unstarted range includes all the new animations.
|
||||||
|
//
|
||||||
|
if (UnstartedSeqno > matchingseqno + 1) {
|
||||||
|
UnstartedSeqno = matchingseqno + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are too many animations in AQ, discard
|
||||||
|
// any very old ones.
|
||||||
|
//
|
||||||
|
if (AQ.Num() > 10) {
|
||||||
|
int ndiscard = AQ.Num() - 10;
|
||||||
|
for (int i = 0; i < ndiscard; i++) {
|
||||||
|
uint64 hash = AQ.First().Hash;
|
||||||
|
HashToSeqno.Remove(hash);
|
||||||
|
AQ.PopFirst();
|
||||||
|
}
|
||||||
|
FirstSeqno += ndiscard;
|
||||||
|
if (UnstartedSeqno < FirstSeqno) {
|
||||||
|
UnstartedSeqno = FirstSeqno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<uint64> FAnimTracker::GetAborted() {
|
||||||
|
TArray<uint64> result;
|
||||||
|
result = std::move(AbortedHashes);
|
||||||
|
AbortedHashes.Empty();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FAnimTracker::AnyUnstarted() {
|
||||||
|
int offset = UnstartedSeqno - FirstSeqno;
|
||||||
|
return (offset < AQ.Num());
|
||||||
|
}
|
||||||
|
|
||||||
|
FAnimStoredStep FAnimTracker::GetUnstarted() {
|
||||||
|
int offset = UnstartedSeqno - FirstSeqno;
|
||||||
|
check(offset < AQ.Num());
|
||||||
|
return AQ[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
void FAnimTracker::Started(uint64 hash) {
|
||||||
|
int offset = UnstartedSeqno - FirstSeqno;
|
||||||
|
check(offset < AQ.Num());
|
||||||
|
check(AQ[offset].Hash == hash);
|
||||||
|
UnstartedSeqno += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "StringDecoder.h"
|
#include "StringDecoder.h"
|
||||||
|
#include "Containers/Deque.h"
|
||||||
|
|
||||||
////////////////////////////////////////////////
|
////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -24,12 +25,25 @@ enum EAnimValueType {
|
|||||||
// The body consists of a sequence of FAnimField
|
// The body consists of a sequence of FAnimField
|
||||||
// records. The body is encoded, to read the
|
// records. The body is encoded, to read the
|
||||||
// FAnimField records you need an FAnimStepDecoder.
|
// FAnimField records you need an FAnimStepDecoder.
|
||||||
|
// This comes in two versions: the string version,
|
||||||
|
// and the string_view version.
|
||||||
//
|
//
|
||||||
////////////////////////////////////////////////
|
////////////////////////////////////////////////
|
||||||
|
|
||||||
struct FAnimStep {
|
struct FAnimStep {
|
||||||
uint32 Hash;
|
uint32 Hash;
|
||||||
std::string_view Body;
|
std::string_view Body;
|
||||||
|
|
||||||
|
FAnimStep() : Hash(0), Body("") {}
|
||||||
|
FAnimStep(uint32 h, std::string_view b) : Hash(h), Body(b) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FAnimStoredStep {
|
||||||
|
uint64 Hash;
|
||||||
|
std::string Body;
|
||||||
|
|
||||||
|
FAnimStoredStep() : Hash(0), Body("") {}
|
||||||
|
FAnimStoredStep(uint32 h, std::string_view b) : Hash(h), Body(b) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////
|
////////////////////////////////////////////////
|
||||||
@@ -106,6 +120,7 @@ public:
|
|||||||
// Initialize the FAnimStepDecoder from the FAnimStep.
|
// Initialize the FAnimStepDecoder from the FAnimStep.
|
||||||
//
|
//
|
||||||
FAnimStepDecoder(const FAnimStep &step) : Decoder(step.Body) {}
|
FAnimStepDecoder(const FAnimStep &step) : Decoder(step.Body) {}
|
||||||
|
FAnimStepDecoder(const FAnimStoredStep& step) : Decoder(step.Body) {}
|
||||||
|
|
||||||
// Return true if the parser has reached the end of the string.
|
// Return true if the parser has reached the end of the string.
|
||||||
//
|
//
|
||||||
@@ -119,3 +134,76 @@ public:
|
|||||||
//
|
//
|
||||||
static FString DebugString(const FAnimStep &step);
|
static FString DebugString(const FAnimStep &step);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// FAnimTracker
|
||||||
|
//
|
||||||
|
// This class monitors the animation queue for a single
|
||||||
|
// tangible. It can identify when a new animation has
|
||||||
|
// appeared on the animation queue, and when animations have
|
||||||
|
// been removed from the animation queue. It also
|
||||||
|
// keeps track of which animations have been started.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
|
||||||
|
class FAnimTracker {
|
||||||
|
public:
|
||||||
|
// Our own copy of the animation queue. We only
|
||||||
|
// store the hashes, not the steps. The First element
|
||||||
|
// of the queue is the oldest item.
|
||||||
|
//
|
||||||
|
TDeque<FAnimStoredStep> AQ;
|
||||||
|
|
||||||
|
// The sequence number of the first item in AQ.
|
||||||
|
//
|
||||||
|
int32 FirstSeqno;
|
||||||
|
|
||||||
|
// Map from hash to sequence number.
|
||||||
|
//
|
||||||
|
TMap<uint64, int32> HashToSeqno;
|
||||||
|
|
||||||
|
// The sequence number of the first unstarted animation.
|
||||||
|
//
|
||||||
|
int32 UnstartedSeqno;
|
||||||
|
|
||||||
|
// Array of recently-aborted hash values.
|
||||||
|
TArray<uint64> AbortedHashes;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Construct a tracker.
|
||||||
|
//
|
||||||
|
// Initially, the tracker is in the empty (Clear) state.
|
||||||
|
//
|
||||||
|
FAnimTracker();
|
||||||
|
|
||||||
|
// Update from the specified animation queue.
|
||||||
|
//
|
||||||
|
// After the update is done, AQ will be a copy
|
||||||
|
// of the animation queue that is passed in.
|
||||||
|
//
|
||||||
|
void Update(std::string_view encqueue);
|
||||||
|
|
||||||
|
// Fetch the aborted hash values.
|
||||||
|
//
|
||||||
|
// This gets the array of aborted hashes and clears
|
||||||
|
// the stored array.
|
||||||
|
//
|
||||||
|
TArray<uint64> GetAborted();
|
||||||
|
|
||||||
|
// Return true if there are any unstarted animation steps.
|
||||||
|
//
|
||||||
|
bool AnyUnstarted();
|
||||||
|
|
||||||
|
// Get the next unstarted animation step.
|
||||||
|
//
|
||||||
|
// You may only call this if AnyUnstarted returns true.
|
||||||
|
//
|
||||||
|
FAnimStoredStep GetUnstarted();
|
||||||
|
|
||||||
|
// Declare that an animation has been started.
|
||||||
|
//
|
||||||
|
// After starting an animation, you should call this.
|
||||||
|
//
|
||||||
|
void Started(uint64 Hash);
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "IntegrationGameModeBase.h"
|
#include "IntegrationGameModeBase.h"
|
||||||
#include "lpx-drvutil.hpp"
|
#include "lpx-drvutil.hpp"
|
||||||
#include "DebugPrint.h"
|
#include "DebugPrint.h"
|
||||||
|
#include "Tangible.h"
|
||||||
#include "TangibleManager.h"
|
#include "TangibleManager.h"
|
||||||
#include "TangibleInterface.h"
|
#include "TangibleInterface.h"
|
||||||
#include "CommonTypes.h"
|
#include "CommonTypes.h"
|
||||||
@@ -120,13 +121,9 @@ void AIntegrationGameModeBase::UpdateTangibles() {
|
|||||||
// Tick all the tangibles.
|
// Tick all the tangibles.
|
||||||
if (EngineSeconds > NextRotateCube) {
|
if (EngineSeconds > NextRotateCube) {
|
||||||
for (int i = 0; i < live.Num(); i++) {
|
for (int i = 0; i < live.Num(); i++) {
|
||||||
AActor* a = TangibleManager.GetTangible(live[i]);
|
UTangible *t = TangibleManager.GetTangible(live[i]);
|
||||||
check(a != nullptr);
|
check(t != nullptr);
|
||||||
bool hasInterface = a->GetClass()->ImplementsInterface(UTangibleInterface::StaticClass());
|
ITangibleInterface::Execute_TurnFromCXX(t->Actor);
|
||||||
if (hasInterface) {
|
|
||||||
ITangibleInterface* iface = Cast<ITangibleInterface>(a);
|
|
||||||
iface->Execute_TurnFromCXX(a);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
NextRotateCube += 0.5;
|
NextRotateCube += 0.5;
|
||||||
}
|
}
|
||||||
|
|||||||
12
Source/Integration/SampleEmptyClass.cpp
Normal file
12
Source/Integration/SampleEmptyClass.cpp
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "SampleEmptyClass.h"
|
||||||
|
|
||||||
|
SampleEmptyClass::SampleEmptyClass()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleEmptyClass::~SampleEmptyClass()
|
||||||
|
{
|
||||||
|
}
|
||||||
15
Source/Integration/SampleEmptyClass.h
Normal file
15
Source/Integration/SampleEmptyClass.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class INTEGRATION_API SampleEmptyClass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SampleEmptyClass();
|
||||||
|
~SampleEmptyClass();
|
||||||
|
};
|
||||||
5
Source/Integration/SampleUObject.cpp
Normal file
5
Source/Integration/SampleUObject.cpp
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "SampleUObject.h"
|
||||||
|
|
||||||
17
Source/Integration/SampleUObject.h
Normal file
17
Source/Integration/SampleUObject.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/NoExportTypes.h"
|
||||||
|
#include "SampleUObject.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class INTEGRATION_API USampleUObject : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
};
|
||||||
5
Source/Integration/Tangible.cpp
Normal file
5
Source/Integration/Tangible.cpp
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Tangible.h"
|
||||||
|
|
||||||
29
Source/Integration/Tangible.h
Normal file
29
Source/Integration/Tangible.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/NoExportTypes.h"
|
||||||
|
#include "AnimQueue.h"
|
||||||
|
#include "TangibleInterface.h"
|
||||||
|
#include "Tangible.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class INTEGRATION_API UTangible : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY()
|
||||||
|
AActor* Actor;
|
||||||
|
|
||||||
|
FAnimTracker AnimTracker;
|
||||||
|
|
||||||
|
void Init(AActor* a) {
|
||||||
|
Actor = a;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
#include "TangibleInterface.generated.h"
|
#include "TangibleInterface.generated.h"
|
||||||
|
|
||||||
// This class does not need to be modified.
|
// This class does not need to be modified.
|
||||||
UINTERFACE(MinimalAPI)
|
UINTERFACE(Blueprintable)
|
||||||
class UTangibleInterface : public UInterface
|
class UTangibleInterface : public UInterface
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
|
|
||||||
#include "TangibleManager.h"
|
#include "TangibleManager.h"
|
||||||
|
#include "TangibleInterface.h"
|
||||||
|
#include "DebugPrint.h"
|
||||||
|
|
||||||
using AActorPtr = AActor*;
|
using namespace DebugPrint;
|
||||||
|
|
||||||
FTangibleManager::FTangibleManager() {
|
FTangibleManager::FTangibleManager() {
|
||||||
World = nullptr;
|
World = nullptr;
|
||||||
@@ -19,8 +21,8 @@ void FTangibleManager::Init(UWorld *world, UClass* tanact) {
|
|||||||
Near = IdView();
|
Near = IdView();
|
||||||
}
|
}
|
||||||
|
|
||||||
AActorPtr FTangibleManager::GetTangible(int64 id) {
|
UTangible *FTangibleManager::GetTangible(int64 id) {
|
||||||
AActorPtr *p = IdToActor.Find(id);
|
UTangible **p = IdToTangible.Find(id);
|
||||||
if (p == nullptr) {
|
if (p == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
} else {
|
} else {
|
||||||
@@ -28,14 +30,17 @@ AActorPtr FTangibleManager::GetTangible(int64 id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AActorPtr FTangibleManager::MakeTangible(int64 id) {
|
UTangible *FTangibleManager::MakeTangible(int64 id) {
|
||||||
AActorPtr& p = IdToActor.FindOrAdd(id);
|
UTangible *& p = IdToTangible.FindOrAdd(id);
|
||||||
if (p == nullptr) {
|
if (p == nullptr) {
|
||||||
FVector location(0,0,0);
|
FVector location(0,0,0);
|
||||||
FRotator rotation(0, 0, 0);
|
FRotator rotation(0, 0, 0);
|
||||||
FActorSpawnParameters params;
|
FActorSpawnParameters params;
|
||||||
p = World->SpawnActor(ClassTangibleActor, &location, &rotation, params);
|
AActor* a = World->SpawnActor(ClassTangibleActor, &location, &rotation, params);
|
||||||
check(p != nullptr);
|
check(a != nullptr);
|
||||||
|
check(a->GetClass()->ImplementsInterface(UTangibleInterface::StaticClass()));
|
||||||
|
p = NewObject<UTangible>();
|
||||||
|
p->Init(a);
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@@ -46,9 +51,9 @@ void FTangibleManager::DeleteTangible(int64 id) {
|
|||||||
|
|
||||||
FTangibleManager::IdArray FTangibleManager::GetLive() {
|
FTangibleManager::IdArray FTangibleManager::GetLive() {
|
||||||
IdArray result;
|
IdArray result;
|
||||||
result.SetNum(IdToActor.Num());
|
result.SetNum(IdToTangible.Num());
|
||||||
int next = 0;
|
int next = 0;
|
||||||
for (auto &pair : IdToActor) {
|
for (auto &pair : IdToTangible) {
|
||||||
result[next++] = pair.Key;
|
result[next++] = pair.Key;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -5,11 +5,9 @@
|
|||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "UObject/NoExportTypes.h"
|
#include "UObject/NoExportTypes.h"
|
||||||
#include "CommonTypes.h"
|
#include "CommonTypes.h"
|
||||||
|
#include "Tangible.h"
|
||||||
#include "TangibleManager.generated.h"
|
#include "TangibleManager.generated.h"
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct INTEGRATION_API FTangibleManager
|
struct INTEGRATION_API FTangibleManager
|
||||||
{
|
{
|
||||||
@@ -28,7 +26,7 @@ public:
|
|||||||
|
|
||||||
// Given a tangible ID, look up actor pointer (or NULL if actor was deleted)
|
// Given a tangible ID, look up actor pointer (or NULL if actor was deleted)
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TMap<int64, AActor*> IdToActor;
|
TMap<int64, UTangible*> IdToTangible;
|
||||||
|
|
||||||
// Actor tangible Id.
|
// Actor tangible Id.
|
||||||
int64 Actor;
|
int64 Actor;
|
||||||
@@ -44,10 +42,10 @@ public:
|
|||||||
void Init(UWorld *world, UClass* tanact);
|
void Init(UWorld *world, UClass* tanact);
|
||||||
|
|
||||||
// Get the tangible if it exists, otherwise return NULL
|
// Get the tangible if it exists, otherwise return NULL
|
||||||
AActor* GetTangible(int64 id);
|
UTangible* GetTangible(int64 id);
|
||||||
|
|
||||||
// Get the tangible if it exists, otherwise create it.
|
// Get the tangible if it exists, otherwise create it.
|
||||||
AActor* MakeTangible(int64 id);
|
UTangible* MakeTangible(int64 id);
|
||||||
|
|
||||||
// Delete the tangible.
|
// Delete the tangible.
|
||||||
void DeleteTangible(int64 id);
|
void DeleteTangible(int64 id);
|
||||||
|
|||||||
Reference in New Issue
Block a user