diff --git a/.gitattributes b/.gitattributes index 90bd4e26..1bded486 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ * text=auto *.bat text eol=crlf +*.sh text eol=lf *.lib filter=lfs diff=lfs merge=lfs -text *.exe filter=lfs diff=lfs merge=lfs -text *.a filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 44540061..940d0336 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,13 @@ gmon.out *.pdb *.sln *.vcproj +.ignore + +Integration.code-workspace +Makefile + +Source/Integration/lpx-*.hpp +Source/Integration/lpx-*.cpp .vscode/** Saved/** diff --git a/Content/MeshStruct.uasset b/Content/MeshStruct.uasset new file mode 100644 index 00000000..201f8829 --- /dev/null +++ b/Content/MeshStruct.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4da1df1663ba609d6eb11c11579b145d91974724b22e07e48d254538c7b4025 +size 3822 diff --git a/Content/NameToMeshTable.uasset b/Content/NameToMeshTable.uasset new file mode 100644 index 00000000..28c6409f --- /dev/null +++ b/Content/NameToMeshTable.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d249735bd7d209a398b187c4ef754ce075af5e308b583fa2a9da9a39e428aa6f +size 2726 diff --git a/Content/TangibleActor.uasset b/Content/TangibleActor.uasset index d3ef1772..781f917d 100644 --- a/Content/TangibleActor.uasset +++ b/Content/TangibleActor.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f55b8b1967273b907cccf5f44a780d091c4bacba418c4d371192c4d0709e07c5 -size 82316 +oid sha256:e25fe70233549efcc19085e8c000dae416a86ae171cb3526039a07a50af1e1a5 +size 290458 diff --git a/Source/Integration/AnimQueue.cpp b/Source/Integration/AnimQueue.cpp index 28983a7a..cea94d5b 100644 --- a/Source/Integration/AnimQueue.cpp +++ b/Source/Integration/AnimQueue.cpp @@ -2,9 +2,11 @@ #include "AnimQueue.h" FlxAnimationStep::FlxAnimationStep(uint64 hash, std::string_view body) { + Finished = false; Hash = hash; Body.SetNum(body.size()); memcpy(Body.GetData(), body.data(), body.size()); + Blueprint = UlxAnimationStepLibrary::AnimationStepGetString(*this, "bp"); } static bool ClearProperties(const FString& prefix, UObject* obj) { @@ -27,32 +29,35 @@ static bool ClearProperties(const FString& prefix, UObject* obj) { return true; } -static bool SetProperty(const FString& name, UObject* obj, const FlxAnimationField& field) { +#pragma optimize("", off) + +static bool SetProperty(const FString& prefix, UObject* obj, const FlxAnimationField& field) { UClass* uclass = obj->GetClass(); - FName nname(name); + FString sname(field.Name.size(), (const UTF8CHAR*)field.Name.data()); + FName nname(prefix + sname); switch (field.Type) { - case ElxAnimValueType::STRING: { + case SimpleDynamicTag::STRING: { FStrProperty* fprop = FindFProperty(uclass, nname); if (fprop == nullptr) return false; FString* pptr = fprop->ContainerPtrToValuePtr(obj); *pptr = FString(field.S.size(), (const UTF8CHAR*)field.S.data()); return true; } - case ElxAnimValueType::NUMBER: { + case SimpleDynamicTag::NUMBER: { FDoubleProperty* fprop = FindFProperty(uclass, nname); if (fprop == nullptr) return false; double* pptr = fprop->ContainerPtrToValuePtr(obj); - fprop->SetPropertyValue(pptr, field.X); + *pptr = field.X; return true; } - case ElxAnimValueType::BOOLEAN: { + case SimpleDynamicTag::BOOLEAN: { FBoolProperty* fprop = FindFProperty(uclass, nname); if (fprop == nullptr) return false; uint8* pptr = fprop->ContainerPtrToValuePtr(obj); fprop->SetPropertyValue(pptr, (field.X == 1.0)); return true; } - case ElxAnimValueType::XYZ: { + case SimpleDynamicTag::VECTOR: { FStructProperty* fprop = FindFProperty(uclass, nname); if (fprop == nullptr) return false; if (fprop->Struct != TBaseStructure::Get()) return false; @@ -64,73 +69,179 @@ static bool SetProperty(const FString& name, UObject* obj, const FlxAnimationFie return false; } -bool FlxAnimationStep::Unpack(const FString& prefix, UObject* into, bool preclear) const { +#pragma optimize("", off) +bool FlxAnimationStep::Unpack(const FString& prefix, UObject* into) const { UClass* uclass = into->GetClass(); std::string_view body((const char*)(Body.GetData()), Body.Num()); FlxAnimationStepDecoder decoder(body); - bool ok = true; - if (preclear) { - ok &= ClearProperties(prefix, into); - } + bool ok = ClearProperties(prefix, into); while (!decoder.AtEOF()) { FlxAnimationField field = decoder.ReadField(); - FString sname(field.Name.size(), (const UTF8CHAR*)field.Name.data()); - ok &= SetProperty(prefix + sname, into, field); + if (Finished && !field.Persistent) continue; + ok &= SetProperty(prefix, into, field); + } + if (Finished) { + FlxAnimationField field; + field.Name = "action"; + field.Persistent = false; + field.Type = SimpleDynamicTag::STRING; + field.S = "idle"; + ok &= SetProperty(prefix, into, field); } return ok; } FString UlxAnimationStepLibrary::AnimationStepDebugString(const FlxAnimationStep& step) { std::string_view body((const char*)(step.Body.GetData()), step.Body.Num()); - return FlxAnimationStepDecoder::DebugString(step.Hash, body); + return FlxAnimationStepDecoder::DebugString(step.Finished, step.Finished, step.Hash, body); } -void UlxAnimationStepLibrary::UnpackAnimationStep(const FlxAnimationStep& step, - const FString& prefix, UObject* into) { - step.Unpack(prefix, into, true); -}; +void UlxAnimationStepLibrary::UnpackAnimationStep(UObject* into, const FlxAnimationStep& step, const FString& prefix) { + step.Unpack(prefix, into); +} -static FlxAnimationField FindAnimationField(const FlxAnimationStep& step, const FString& name) { +static FlxAnimationField FindAnimationFieldLL(const FlxAnimationStep& step, std::string_view name) { std::string_view body((const char*)(step.Body.GetData()), step.Body.Num()); - FTCHARToUTF8 utf8name(name); - std::string_view uname(utf8name.Get(), utf8name.Length()); FlxAnimationStepDecoder decoder(body); FlxAnimationField result; while (!decoder.AtEOF()) { result = decoder.ReadField(); - if (result.Name == uname) { + if (result.Name == name) { return result; } } - result.Type = ElxAnimValueType::INVALID; + result.Type = SimpleDynamicTag::UNINITIALIZED; return result; } +static FlxAnimationField FindAnimationField(const FlxAnimationStep& step, const FString& name) { + FTCHARToUTF8 utf8name(name); + std::string_view uname(utf8name.Get(), utf8name.Length()); + return FindAnimationFieldLL(step, uname); +} + +void FlxAnimationStep::AutoUpdateXYZ(AActor *actor) const { + FlxAnimationField xyz = FindAnimationFieldLL(*this, "xyz"); + if (xyz.Type == SimpleDynamicTag::VECTOR) { + actor->SetActorLocation(FVector(xyz.X, xyz.Y, xyz.Z)); + } +} + +void FlxAnimationStep::AutoUpdateFacing(AActor *actor) const { + FlxAnimationField facing = FindAnimationFieldLL(*this, "facing"); + if (facing.Type == SimpleDynamicTag::NUMBER) { + actor->SetActorRotation(FRotator(0, facing.X, 0)); + } +} + +void FlxAnimationStep::AutoUpdatePlane(FName *planep) const { + FlxAnimationField plane = FindAnimationFieldLL(*this, "plane"); + if (plane.Type == SimpleDynamicTag::STRING) { + FString pname(plane.S.size(), (const UTF8CHAR*)(plane.S.data())); + *planep = FName(pname); + } +} + +bool UlxAnimationStepLibrary::AnimationStepEqual(const FlxAnimationStep &stepa, const FlxAnimationStep &stepb) { + return (stepa.Finished == stepb.Finished) && (stepa.Hash == stepb.Hash); +} + +bool UlxAnimationStepLibrary::AnimationStepIsIdle(const FlxAnimationStep &step) { + return step.Finished; +} + FVector UlxAnimationStepLibrary::AnimationStepGetVector(const FlxAnimationStep& step, const FString& name) { FlxAnimationField field = FindAnimationField(step, name); - if (field.Type != ElxAnimValueType::XYZ) return FVector(0, 0, 0); + if (field.Type != SimpleDynamicTag::VECTOR) return FVector(0, 0, 0); return FVector(field.X, field.Y, field.Z); } double UlxAnimationStepLibrary::AnimationStepGetFloat(const FlxAnimationStep& step, const FString& name) { FlxAnimationField field = FindAnimationField(step, name); - if (field.Type != ElxAnimValueType::NUMBER) return 0.0; + if (field.Type != SimpleDynamicTag::NUMBER) return 0.0; return field.X; } -#pragma optimize("", off) FString UlxAnimationStepLibrary::AnimationStepGetString(const FlxAnimationStep& step, const FString& name) { + if (step.Finished && (name == TEXT("action"))) { + return TEXT("idle"); + } FlxAnimationField field = FindAnimationField(step, name); - if (field.Type != ElxAnimValueType::STRING) return TEXT(""); + if (field.Type != SimpleDynamicTag::STRING) return TEXT(""); return FString(field.S.size(), (const UTF8CHAR*)(field.S.data())); } bool UlxAnimationStepLibrary::AnimationStepGetBool(const FlxAnimationStep& step, const FString& name) { FlxAnimationField field = FindAnimationField(step, name); - if (field.Type != ElxAnimValueType::BOOLEAN) return false; + if (field.Type != SimpleDynamicTag::BOOLEAN) return false; return field.X == 1.0; } + +FlxAnimationField FlxAnimationStepDecoder::ReadField() { + FlxAnimationField result; + result.Name = Decoder.read_string_view(); + result.Persistent = Decoder.read_bool(); + result.Type = (SimpleDynamicTag)Decoder.read_uint8(); + switch (result.Type) { + case SimpleDynamicTag::STRING: { + result.S = Decoder.read_string_view(); + break; + } + case SimpleDynamicTag::NUMBER: { + result.X = Decoder.read_double(); + break; + } + case SimpleDynamicTag::BOOLEAN: { + result.X = Decoder.read_bool() ? 1.0 : 0.0; + break; + } + case SimpleDynamicTag::VECTOR: { + result.X = Decoder.read_double(); + result.Y = Decoder.read_double(); + result.Z = Decoder.read_double(); + break; + } + default: { + Decoder.read_bytes(Decoder.fill()); + result.Type = SimpleDynamicTag::UNINITIALIZED; + break; + } + } + return result; +} + +FString FlxAnimationStepDecoder::DebugString(bool injectidle, bool persistentonly, uint64 hash, std::string_view body) { + FString result; + FlxAnimationStepDecoder decoder(body); + result.Appendf(TEXT("Hash=%016llx"), hash); + if (injectidle) { + result.Appendf(TEXT(" action=idle")); + } + while (!decoder.AtEOF()) { + FlxAnimationField field = decoder.ReadField(); + if (persistentonly && !field.Persistent) continue; + result.Append(TEXT(" ")); + result.Append(FString(field.Name.size(), (const UTF8CHAR*)field.Name.data())); + result.Append(field.Persistent ? TEXT("=") : TEXT(":")); + switch (field.Type) { + case SimpleDynamicTag::STRING: + result.Append(FString(field.S.size(), (const UTF8CHAR*)field.S.data())); + break; + case SimpleDynamicTag::NUMBER: + result.Appendf(TEXT("%lf"), field.X); + break; + case SimpleDynamicTag::BOOLEAN: + result.Append((field.X) == 1.0 ? TEXT("true") : TEXT("false")); + break; + case SimpleDynamicTag::VECTOR: + result.Appendf(TEXT("%lf,%lf,%lf"), field.X, field.Y, field.Z); + break; + } + } + return result; +} + FlxAnimationStepView FlxAnimQueueDecoder::ReadStep() { FlxAnimationStepView result; result.Hash = Decoder.read_uint64(); @@ -138,65 +249,16 @@ FlxAnimationStepView FlxAnimQueueDecoder::ReadStep() { return result; } -FlxAnimationField FlxAnimationStepDecoder::ReadField() { - FlxAnimationField result; - result.Name = Decoder.read_string_view(); - result.Persistent = Decoder.read_bool(); - result.Type = (ElxAnimValueType)Decoder.read_uint8(); - switch (result.Type) { - case ElxAnimValueType::STRING: { - result.S = Decoder.read_string_view(); - break; - } - case ElxAnimValueType::NUMBER: { - result.X = Decoder.read_double(); - break; - } - case ElxAnimValueType::BOOLEAN: { - result.X = Decoder.read_bool() ? 1.0 : 0.0; - break; - } - case ElxAnimValueType::XYZ: { - result.X = Decoder.read_double(); - result.Y = Decoder.read_double(); - result.Z = Decoder.read_double(); - break; - } - default: { - Decoder.set_at_eof(); - result.Type = ElxAnimValueType::BOOLEAN; - result.X = 0; - break; - } - } +uint64 FlxAnimQueueDecoder::PeekHash() { + int64_t read_count = Decoder.total_reads(); + uint64 result = Decoder.read_uint64(); + Decoder.unread_to(read_count); return result; } -FString FlxAnimationStepDecoder::DebugString(uint64 hash, std::string_view body) { - FString result; - FlxAnimationStepDecoder decoder(body); - result.Appendf(TEXT("Hash=%016llx"), hash); - while (!decoder.AtEOF()) { - FlxAnimationField field = decoder.ReadField(); - result.Append(TEXT(" ")); - result.Append(FString(field.Name.size(), (const UTF8CHAR*)field.Name.data())); - result.Append(field.Persistent ? TEXT("=") : TEXT(":")); - switch (field.Type) { - case ElxAnimValueType::STRING: - result.Append(FString(field.S.size(), (const UTF8CHAR*)field.S.data())); - break; - case ElxAnimValueType::NUMBER: - result.Appendf(TEXT("%lf"), field.X); - break; - case ElxAnimValueType::BOOLEAN: - result.Append((field.X) == 1.0 ? TEXT("true") : TEXT("false")); - break; - case ElxAnimValueType::XYZ: - result.Appendf(TEXT("%lf,%lf,%lf"), field.X, field.Y, field.Z); - break; - } - } - return result; +FlxAnimQueueDecoder::FlxAnimQueueDecoder(std::string_view queue) : Decoder(queue) { + SizeLimit = Decoder.read_uint8(); + ActualSize = Decoder.read_uint8(); } FString FlxAnimQueueDecoder::DebugString(std::string_view queue) { @@ -204,42 +266,62 @@ FString FlxAnimQueueDecoder::DebugString(std::string_view queue) { FlxAnimQueueDecoder decoder(queue); while (!decoder.AtEOF()) { FlxAnimationStepView step = decoder.ReadStep(); - FString stepdebug = FlxAnimationStepDecoder::DebugString(step.Hash, step.Body); + FString stepdebug = FlxAnimationStepDecoder::DebugString(false, false, step.Hash, step.Body); result.Appendf(TEXT("%s\n"), *stepdebug); } return result; } - FlxAnimTracker::FlxAnimTracker() { + Clear(); +} + +void FlxAnimTracker::Clear() { AQ.Empty(); - FirstSeqno = 0; - HashToSeqno.Empty(); - UnstartedSeqno = 0; - PlaybackMode = ElxAnimationMode::INVALID; - AbortedHashes.Empty(); + Changed = true; +} + +void FlxAnimTracker::FinishedAnimation(uint64 hash) { + for (int i = 0; i < AQ.Num(); i++) { + if (AQ[i].Hash == hash) { + AQ[i].Finished = true; + Changed = true; + } + } +} + +void FlxAnimTracker::SkipToEnd() { + for (int i = 0; i < AQ.Num(); i++) { + if (!AQ[i].Finished) { + AQ[i].Finished = true; + Changed = true; + } + } } void FlxAnimTracker::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. + check(!encqueue.empty()); + + // If the first hash matches, we don't bother updating at all. // - if (encqueue.empty()) { - if (AQ.IsEmpty()) { + FlxAnimQueueDecoder decoder(encqueue); + if (!AQ.IsEmpty()) { + if (decoder.PeekHash() == AQ.Last().Hash) { return; } - } else { - if (!AQ.IsEmpty()) { - FlxStringDecoder qdecoder(encqueue); - uint64 hash = qdecoder.read_uint64(); - if (hash == AQ.Last().Hash) { - return; - } - } + } + + + // The Changed flag is set whenever there is any change to + // the animation queue of any kind. + // + Changed = true; + + // Build a map indexing the steps in AQ. + // + TMap HashToIndex; + for (int i = 0; i < AQ.Num(); i++) { + HashToIndex.Emplace(AQ[i].Hash, i); } // Search for a Luprex animation record that matches @@ -249,108 +331,59 @@ void FlxAnimTracker::Update(std::string_view encqueue) { // At the same time, push all non-matching records // after the matching record onto a stack of new records. // - FlxAnimQueueDecoder decoder(encqueue); TArray newsteps; - int32 matchingseqno = -1; + int32 matchingindex = -1; while (!decoder.AtEOF()) { FlxAnimationStepView step = decoder.ReadStep(); - int32* stepseq = HashToSeqno.Find(step.Hash); - if (stepseq == nullptr) { + int32* indexp = HashToIndex.Find(step.Hash); + if (indexp == nullptr) { newsteps.Emplace(step); } else { - matchingseqno = *stepseq; + matchingindex = *indexp; break; } } // Remove all animations after the most recent matching - // record. If we remove a 'started' animation, add that - // animation to the list of aborted animations. + // record. // - int32 nremove = (FirstSeqno + AQ.Num()) - (matchingseqno + 1); + int32 nremove = (AQ.Num() - (matchingindex + 1)); check((nremove >= 0) && (nremove <= AQ.Num())); for (int32 i = 0; i < nremove; i++) { - uint64 lasthash = AQ.Last().Hash; - int32 seqno = FirstSeqno + AQ.Num() - 1; - HashToSeqno.Remove(lasthash); - if (seqno < UnstartedSeqno) { - AbortedHashes.Emplace(lasthash); - } AQ.PopLast(); } - // If we aborted a 'started' animation, we have to fix - // up the Unstarted animation pointer. - // - // Note: this could leave the Unstarted pointer at - // seqno less than FirstSeqno. We will fix that state - // of affairs up later. - // - if (UnstartedSeqno > (FirstSeqno + AQ.Num())) { - UnstartedSeqno = matchingseqno; - PlaybackMode = ElxAnimationMode::BlendToFinal; - } - // Transfer the new animations onto the queue. // while (!newsteps.IsEmpty()) { FlxAnimationStepView step = newsteps.Pop(); - int32 seqno = FirstSeqno + AQ.Num(); AQ.EmplaceLast(step.Hash, step.Body); - HashToSeqno.Emplace(step.Hash, seqno); + } // 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 += 1; - } - } - - // If UnstartedSeqno is before the live range, - // then the whole queue has to be replayed. Don't - // do that: just fast skip to the end. + // TODO: this is hardwired to keep 10. Instead, we + // should keep the number specified in the queue. // - if (UnstartedSeqno <= FirstSeqno) { - if (AQ.Num() == 0) { - UnstartedSeqno = FirstSeqno; - } else { - UnstartedSeqno = FirstSeqno + AQ.Num() - 1; + int limit = 10; + int ndiscard = AQ.Num() - limit; + if (ndiscard > 0) { + for (int i = 0; i < ndiscard; i++) { + AQ.PopFirst(); } - PlaybackMode = ElxAnimationMode::WarpToFinal; } } -TArray FlxAnimTracker::GetAborted() { - TArray result; - result = std::move(AbortedHashes); - AbortedHashes.Empty(); +FlxAnimationStep FlxAnimTracker::GetCurrentAnimation() { + FlxAnimationStep result; + for (int i = 0; i < AQ.Num(); i++) { + if (!AQ[i].Finished) { + return AQ[i]; + } + } + result = AQ.Last(); + result.Hash = 0; return result; } - -ElxAnimationMode FlxAnimTracker::GetNextStep(FlxAnimationStep &step) { - int offset = UnstartedSeqno - FirstSeqno; - if (offset < AQ.Num()) { - step = AQ[offset]; - return PlaybackMode; - } else { - step.Hash = 0; - step.Body.Empty(); - return ElxAnimationMode::INVALID; - } -} - -void FlxAnimTracker::StartedStep(uint64 hash) { - int offset = UnstartedSeqno - FirstSeqno; - check(offset < AQ.Num()); - check(AQ[offset].Hash == hash); - UnstartedSeqno += 1; - PlaybackMode = ElxAnimationMode::StartAnimation; -} - diff --git a/Source/Integration/AnimQueue.h b/Source/Integration/AnimQueue.h index 0508a0d1..105fc882 100644 --- a/Source/Integration/AnimQueue.h +++ b/Source/Integration/AnimQueue.h @@ -5,46 +5,6 @@ #include "Containers/Deque.h" #include "AnimQueue.generated.h" -//////////////////////////////////////////////// -// -// This is copied over from Luprex source. Not ideal. -// -//////////////////////////////////////////////// - -enum class ElxAnimValueType { - STRING, - NUMBER, - BOOLEAN, - XYZ, - INVALID -}; - -//////////////////////////////////////////////// -// -// Playback Modes -// -// There are three different ways to play an animation: -// -// 1. Start Animation. This is the normal way to play an animation. -// -// 2. Warp to Final. Skip the actual animation, and jump -// instantaneously to the final state of the animation. -// -// 3. Blend to Final. Skip the actual animation, and blend -// smoothly to the final state of the animation. If the current -// state is very far from the final state, then maybe -// jump instantaneously instead. -// -//////////////////////////////////////////////// - -UENUM(BlueprintType) -enum class ElxAnimationMode : uint8 { - StartAnimation, - WarpToFinal, - BlendToFinal, - INVALID, -}; - //////////////////////////////////////////////// // // An single animation step. @@ -61,13 +21,19 @@ struct INTEGRATION_API FlxAnimationStep { GENERATED_BODY() public: + UPROPERTY() + bool Finished; + UPROPERTY() uint64 Hash; UPROPERTY() TArray Body; - FlxAnimationStep() : Hash(0), Body() {} + UPROPERTY() + FString Blueprint; + + FlxAnimationStep() : Finished(false), Hash(0), Body(), Blueprint() {} FlxAnimationStep(uint64 h, std::string_view b); // Unpack an AnimStep into a UObject @@ -78,6 +44,14 @@ public: // routine tries to find a string property "color" in the // UObject, and then it sets that property to "blue." // + // The prefix is prepended to the key names. For example, + // if one of the key-value pairs is "color=blue", and the + // prefix is "aq", then the property "aqColor=blue" will be + // stored in the UObject. + // + // All properties of the UObject starting with the specified + // prefix are cleared before unpacking the animation step. + // // Returns true if all of the key-value pairs in the // animation step could be unpacked into fields of the UObject. // This could fail, for instance, if the UObject just doesn't @@ -85,16 +59,23 @@ public: // fail if there's a type mismatch. For example, "color=blue" // cannot be stored in a property "color" of type int. // - // The prefix is prepended to the key names. For example, - // if one of the key-value pairs is "color=blue", and the - // prefix is "aq", then the property "aqColor=blue" will be - // stored in the UObject. + // Automatically injects a boolean property "idle" representing + // whether the property's finished flag is set. // - // If pre-clear is true, then all properties of the UObject - // starting with the specified prefix are cleared before - // unpacking the animation step. + bool Unpack(const FString& prefix, UObject* into) const; + + // Auto-Execute // - bool Unpack(const FString& prefix, UObject* into, bool preclear = true) const; + // These functions automatically update certain actor + // properties: + // + // AutoUpdateXYZ(AActor *actor); // uses 'xyz' keyword + // AutoUpdateFacing(AActor *actor); // uses 'facing' keyword. + // AutoUpdatePlane(FName *plane); // uses 'plane' keyword + // + void AutoUpdateXYZ(AActor *actor) const; + void AutoUpdateFacing(AActor *actor) const; + void AutoUpdatePlane(FName *plane) const; }; //////////////////////////////////////////////// @@ -114,10 +95,15 @@ public: UFUNCTION(BlueprintCallable, BlueprintPure, Category = Luprex) static FString AnimationStepDebugString(const FlxAnimationStep& step); - UFUNCTION(BlueprintCallable, Category = Luprex) - static void UnpackAnimationStep(const FlxAnimationStep& step, - const FString& VariableNamePrefix, UObject* into); + UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "into"), Category = Luprex) + static void UnpackAnimationStep(UObject* into, const FlxAnimationStep& step, const FString& VariableNamePrefix = TEXT("aq")); + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex") + static bool AnimationStepEqual(const FlxAnimationStep &StepA, const FlxAnimationStep &StepB); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex") + static bool AnimationStepIsIdle(const FlxAnimationStep &step); + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex") static FVector AnimationStepGetVector(const FlxAnimationStep& step, const FString& name); @@ -129,18 +115,13 @@ public: UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex") static bool AnimationStepGetBool(const FlxAnimationStep& step, const FString& name); - }; //////////////////////////////////////////////// // -// Exposing functions to blueprints. // //////////////////////////////////////////////// -//UFUNCTION(BlueprintCallable) -//void Unpack(const FString& prefix, UObject* into, bool preclear = true); - struct FlxAnimationStepView { uint64 Hash; std::string_view Body; @@ -166,7 +147,7 @@ struct FlxAnimationStepView { struct FlxAnimationField { std::string_view Name; bool Persistent; - ElxAnimValueType Type; + SimpleDynamicTag Type; double X, Y, Z; std::string_view S; }; @@ -184,21 +165,38 @@ struct FlxAnimationField { class FlxAnimQueueDecoder { private: - FlxStringDecoder Decoder; + FlxStreamBuffer Decoder; + + // These values are immediately read from the header. + // + int SizeLimit; + int ActualSize; public: // Initialize the FlxAnimQueueDecoder with the encoded animation queue. // - FlxAnimQueueDecoder(std::string_view s) : Decoder(s) {} + FlxAnimQueueDecoder(std::string_view s); + + // Get the size limit of the animation queue. + // + int GetSizeLimit() const { return SizeLimit; } + + // Get the Actual Size of the animation queue. + // + int GetActualSize() const { return ActualSize; } // Return true if the parser has reached the end of the string. // - bool AtEOF() { return Decoder.at_eof(); } + bool AtEOF() { return Decoder.empty(); } // Read one animation step. // FlxAnimationStepView ReadStep(); + // Peek at the hash of the next animation step. + // + uint64 PeekHash(); + // Convert an AnimQueue to an FString. // static FString DebugString(std::string_view s); @@ -217,7 +215,7 @@ public: class FlxAnimationStepDecoder { private: - FlxStringDecoder Decoder; + FlxStreamBuffer Decoder; public: // Initialize the FlxAnimationStepDecoder from the FlxAnimationStepView. @@ -226,7 +224,7 @@ public: // Return true if the parser has reached the end of the string. // - bool AtEOF() { return Decoder.at_eof(); } + bool AtEOF() { return Decoder.empty(); } // Read one field. // @@ -234,7 +232,7 @@ public: // Convert an AnimStep to an FString. // - static FString DebugString(uint64 hash, std::string_view body); + static FString DebugString(bool injectidle, bool persistentonly, uint64 hash, std::string_view body); }; //////////////////////////////////////////////// @@ -257,26 +255,11 @@ public: // TDeque AQ; - // The sequence number of the first item in AQ. + // True if something has recently changed. // - int32 FirstSeqno; - - // Map from hash to sequence number. - // - TMap HashToSeqno; - - // The sequence number of the first unstarted animation. - // - int32 UnstartedSeqno; - - // Indicates whether the unstarted animation should be played or otherwise. - // - ElxAnimationMode PlaybackMode; - - // Array of recently-aborted hash values. - // - TArray AbortedHashes; + bool Changed; +private: public: // Construct a tracker. // @@ -284,6 +267,10 @@ public: // FlxAnimTracker(); + // Clear everything, reset to the initial state. + // + void Clear(); + // Update from the specified animation queue. // // After the update is done, AQ will be a copy @@ -291,24 +278,39 @@ public: // void Update(std::string_view encqueue); - // Fetch the aborted hash values. + // Get the current animation step. // - // This gets the array of aborted hashes and clears - // the stored array. - // - TArray GetAborted(); + // Get the current animation step. This is the step that the + // blueprint should currently be playing. + // + FlxAnimationStep GetCurrentAnimation(); - // Get the next unstarted animation step. + // Declare that an animation is finished. // - // Get the next animation step. Returns the next step and the - // playback mode. If the playback mode is INVALID then there is - // no next step to play - // - ElxAnimationMode GetNextStep(FlxAnimationStep& step); + // The blueprint uses this function call to indicate that it + // is done playing the specified animation. This will cause the + // animation to be marked as finished, which in turn causes + // 'GetCurrentStep' to advance to the next animation. + // + void FinishedAnimation(uint64 Hash); - // Declare that an animation has been started. + // Skip to the end of the animation queue. + // + // This is equivalent to calling 'FinishedHash' on every + // animation in the entire queue. // - // After starting an animation, you should call this. + void SkipToEnd(); + + // Clear the 'Changed' flag. // - void StartedStep(uint64 Hash); + void ClearChanged() { Changed = false; } + + // Get the 'Changed' flag. + // + // The changed flag is set to true whenever the Luprex animation + // queue changes from its previous state. The changed flag is also + // set to true whenever 'SetFinished' marks an animation as finished. + // The changed flag can only be set to false by 'ClearChanged,' above. + // + bool IsChanged() const { return Changed; } }; \ No newline at end of file diff --git a/Source/Integration/IntegrationGameModeBase.cpp b/Source/Integration/IntegrationGameModeBase.cpp index 2f6dfb69..ce4d277b 100644 --- a/Source/Integration/IntegrationGameModeBase.cpp +++ b/Source/Integration/IntegrationGameModeBase.cpp @@ -2,10 +2,10 @@ #include "IntegrationGameModeBase.h" #include "lpx-drvutil.hpp" +#include "lpx-paths.hpp" #include "DebugPrint.h" #include "Tangible.h" #include "TangibleManager.h" -#include "TangibleInterface.h" #include "CommonTypes.h" #include "AnimQueue.h" #include @@ -16,6 +16,7 @@ using namespace CommonTypes; AIntegrationGameModeBase::AIntegrationGameModeBase() { + TangibleManager = NewObject(); EngineSeconds = 0.0; NextThreadTrigger = 1.0; //PrimaryActorTick.bCanEverTick = true; // Probably wrong @@ -47,6 +48,11 @@ void AIntegrationGameModeBase::ResetToInitialState() { Playing = false; + if (TangibleManager != nullptr) { + TangibleManager->ConditionalBeginDestroy(); + TangibleManager = nullptr; + } + // Shut down the thread LuprexUpdateTask.Shutdown(); @@ -104,67 +110,73 @@ void AIntegrationGameModeBase::MaybeTriggerUpdateTask(float deltaseconds) { } } -//#pragma optimize( "", off ) -//void SetLocal(UObject* obj, const char *name, int value) { -// FString sname((const UTF8CHAR *)name); -// FName nname(sname); -// UClass* uclass = obj->GetClass(); -// FProperty* fprop = FindFProperty(uclass, nname); -// FStructProperty* sprop = FindFProperty(uclass, nname); -//} + + +#pragma optimize("", off) void AIntegrationGameModeBase::UpdateTangibles() { + double radius = 1000.0; // Hardwired for now. + using TanArray = UlxTangibleManager::TanArray; if (!Playing) return; FlxLockedWrapper w(LockableWrapper); - int64 actor = w.GetActor(); - TangibleManager.SetActor(actor); - TangibleManager.SetNear(w.GetNear(actor, 100, 100, 100)); - for (int64 id : TangibleManager.GetNear()) { - TangibleManager.MakeTangible(id); + int64 player = w.GetActor(); + IdView nearids = w.GetNear(player, radius, radius, radius); + TangibleManager->UpdateNearAccordingToLuprex(nearids); + TanArray alltans = TangibleManager->GetAllTangibles(); + IdArray allids = TangibleManager->GetIds(alltans); + StringViewVec allqueues = w.GetAnimationQueues(allids); + for (int i = 0; i < alltans.Num(); i++) { + alltans[i]->UpdateAnimationQueue(allqueues[i]); } - // Update animation queues of live tangibles. - IdArray tanids = TangibleManager.GetLive(); - StringViewVec aqueues = w.GetAnimationQueues(tanids); - for (int i = 0; i < tanids.Num(); i++) { - uint64_t tanid = tanids[i]; - std::string_view aqueue = aqueues[i]; - UlxTangible* t = TangibleManager.GetTangible(tanid); - check(t != nullptr); - t->AnimTracker.Update(aqueue); + TangibleManager->RecalcNearAccordingToUnreal(player, radius); + TangibleManager->DeleteFarawayTangibles(); +} - TArray aborted = t->AnimTracker.GetAborted(); - for (uint64 hash : aborted) { - IlxTangibleInterface::Execute_AbortAnimation(t->Actor, hash); - } - FlxAnimationStep step; - ElxAnimationMode mode = t->AnimTracker.GetNextStep(step); - if (mode != ElxAnimationMode::INVALID) { - bool started = IlxTangibleInterface::Execute_StartAnimation(t->Actor, mode, step); - if (started) { - t->AnimTracker.StartedStep(step.Hash); - } - } +void AIntegrationGameModeBase::ExecuteDebuggingCommand(const FString &fs) { + if (fs == "\\invokeplayer") { + DPrint(TEXT("Trying to invoke 'myfunction' in lua")); + FlxLockedWrapper w(LockableWrapper); + FlxStreamBuffer sb; + sb.write_string("myfunction"); + sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER); + sb.write_double(3.0); + sb.write_simple_dynamic_tag(SimpleDynamicTag::STRING); + sb.write_string("Howdy"); + sb.write_simple_dynamic_tag(SimpleDynamicTag::VECTOR); + sb.write_fvector(FVector(2,3,4)); + std::string_view datapk = sb.view(); + int64 player = w.GetActor(); + w->play_invoke_engio(w.Get(), player, datapk.size(), datapk.data()); + } else { + ConsoleOutput.AppendLine(TEXT("Unknown Command")); } } + void AIntegrationGameModeBase::ConsoleSendInput(const FString& fs) { FlxLockedWrapper w(LockableWrapper); if (w->engine != nullptr) { - const TCHAR* fstchar = *fs; - if (sizeof(TCHAR) == 2) - { - ConsoleOutput.AppendLine(FString("> ") + fs); - std::u16string_view fsview((const char16_t*)fstchar, fs.Len()); - std::string utf8 = drvutil::utf16_to_utf8(fsview); - utf8 = utf8 + "\n"; - w->play_recv_incoming(w.Get(), 0, utf8.size(), utf8.c_str()); + ConsoleOutput.AppendLine(FString("> ") + fs); + // This is a bad way to do this. The problem is that if some + // lua code contains '\\', we'll catch it instead of passing it + // through. There's no simple solution, though. + if (fs[0] == '\\') { + ExecuteDebuggingCommand(fs); + } else { + const TCHAR* fstchar = *fs; + if (sizeof(TCHAR) == 2) + { + std::u16string_view fsview((const char16_t*)fstchar, fs.Len()); + std::string utf8 = drvutil::utf16_to_utf8(fsview); + utf8 = utf8 + "\n"; + w->play_recv_incoming(w.Get(), 0, utf8.size(), utf8.c_str()); + } } } } - void AIntegrationGameModeBase::Tick(float deltaseconds) { Super::Tick(deltaseconds); @@ -209,22 +221,25 @@ void AIntegrationGameModeBase::BeginPlay() if (w->play_initialize != nullptr) { drvutil::ostringstream srcpak; - std::string srcpakerr = drvutil::package_lua_source("c:\\Luprex", &srcpak); + std::string srcpakerr = drvutil::package_lua_source(LUPREX_ROOT_PATH, &srcpak); if (!srcpakerr.empty()) { DPrint(srcpakerr.c_str()); } - std::string_view srcpakv = srcpak.view(); - char* argv[1]; - argv[0] = const_cast("lpxserver"); - w->play_initialize(w.Get(), 1, argv, srcpakv.size(), srcpakv.data(), ""); - if (w->error[0]) + else { - DPrint(w->error); - } - if (w->engine != nullptr) { - DPrint("Luprex initialize success"); - Playing = true; + std::string_view srcpakv = srcpak.view(); + char* argv[1]; + argv[0] = const_cast("lpxserver"); + w->play_initialize(w.Get(), 1, argv, srcpakv.size(), srcpakv.data(), ""); + if (w->error[0]) + { + DPrint(w->error); + } + if (w->engine != nullptr) { + DPrint("Luprex initialize success"); + Playing = true; + } } } @@ -237,7 +252,8 @@ void AIntegrationGameModeBase::BeginPlay() } // Initialize the tangible manager. - TangibleManager.Init(GetWorld(), ClassTangibleActor); + TangibleManager = NewObject(); + TangibleManager->Init(GetWorld(), ClassTangibleActor); } void AIntegrationGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason) @@ -245,4 +261,3 @@ void AIntegrationGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason) ResetToInitialState(); } - diff --git a/Source/Integration/IntegrationGameModeBase.h b/Source/Integration/IntegrationGameModeBase.h index ca584543..e6c5ff5a 100644 --- a/Source/Integration/IntegrationGameModeBase.h +++ b/Source/Integration/IntegrationGameModeBase.h @@ -43,6 +43,9 @@ public: UPROPERTY(EditDefaultsOnly, Category = "Luprex") TSubclassOf ClassTangibleActor; + // Execute a debugging command, typed on the GUI. + void ExecuteDebuggingCommand(const FString &fs); + // Transfer console output from the Luprex engine to unreal. void UpdateConsoleOutput(); @@ -57,7 +60,7 @@ public: virtual uint32 Run() override; UPROPERTY() - FTangibleManager TangibleManager; + UlxTangibleManager *TangibleManager; // This stores the entire text currently visible in the console. FlxConsoleOutput ConsoleOutput; diff --git a/Source/Integration/LockedWrapper.cpp b/Source/Integration/LockedWrapper.cpp index ddc53146..0b1a4432 100644 --- a/Source/Integration/LockedWrapper.cpp +++ b/Source/Integration/LockedWrapper.cpp @@ -2,6 +2,7 @@ #include "LockedWrapper.h" #include "DebugPrint.h" #include "lpx-drvutil.hpp" +#include "lpx-paths.hpp" using namespace CommonTypes; @@ -10,7 +11,9 @@ void FlxLockedWrapper::InitWrapper() { // Already initialized. return; } - void* DLL = FPlatformProcess::GetDllHandle(TEXT("c:\\Luprex\\build\\visual\\luprexlib.dll")); + FString dll((const UTF8CHAR*)LUPREX_DLL_PATH); + DebugPrint::DPrint(dll); + void* DLL = FPlatformProcess::GetDllHandle(*dll); if (DLL != nullptr) { using InitFn = void (*)(EngineWrapper*); InitFn init = (InitFn)FPlatformProcess::GetDllExport(DLL, TEXT("init_engine_wrapper")); @@ -43,7 +46,7 @@ int64 FlxLockedWrapper::GetActor() { IdView FlxLockedWrapper::GetNear(int64 id, double rx, double ry, double rz) { uint32 size; int64* data; - Lockable.Wrapper.get_tangibles_near(Get(), id, rx, ry, rz, &size, &data); + Lockable.Wrapper.get_tangibles_near(Get(), id, rx, ry, rz, &size, (int64_t**)&data); return IdView(data, size); } @@ -64,7 +67,7 @@ StringViewVec FlxLockedWrapper::GetAnimationQueues(IdView ids) { const char** StrBuf = Lockable.AQStrBuffer.GetData(); // Get the animation queues into the static buffers. - Lockable.Wrapper.get_animation_queues(Get(), num, ids.GetData(), LenBuf, StrBuf); + Lockable.Wrapper.get_animation_queues(Get(), num, (const int64_t *)ids.GetData(), LenBuf, StrBuf); // Transfer data from static buffers into an array of string_view StringViewVec result; @@ -73,4 +76,4 @@ StringViewVec FlxLockedWrapper::GetAnimationQueues(IdView ids) { result[i] = std::string_view(StrBuf[i], LenBuf[i]); } return result; -} \ No newline at end of file +} diff --git a/Source/Integration/LuprexSockets.cpp b/Source/Integration/LuprexSockets.cpp index a7d213c4..4ad0c0fc 100644 --- a/Source/Integration/LuprexSockets.cpp +++ b/Source/Integration/LuprexSockets.cpp @@ -23,9 +23,13 @@ THIRD_PARTY_INCLUDES_START THIRD_PARTY_INCLUDES_END #undef UI +#ifdef __linux__ +#else #define WIN32_LEAN_AND_MEAN #include #include +#endif + #include #include #include @@ -241,6 +245,26 @@ public: // ///////////////////////////////////////////////////////////////// +#ifdef __linux__ +inline static void strerror_helper(int status, int errnum, char errbuf[256]) { + if (status != 0) { + snprintf(errbuf, 256, "unknown errno %d", errnum); + } +} + +inline static void strerror_helper(const char *result, int errnum, char errbuf[256]) { + if (result != errbuf) { + snprintf(errbuf, 256, "%s", result); + } +} + +static std::string strerror_str(int errnum) { + char buf[256]; + auto rval = strerror_r(errnum, buf, 256); + strerror_helper(rval, errnum, buf); + return buf; +} +#else static std::string strerror_str(int errnum) { char buf[256]; int status = strerror_s(buf, 256, errnum); @@ -250,6 +274,8 @@ static std::string strerror_str(int errnum) { } return buf; } +#endif + static FSocket* OpenConnection(ISocketSubsystem *subsys, const std::string& host, const std::string& port, std::string& err) { @@ -401,6 +427,12 @@ static SSL_CTX* SSLNewContext(int verify, const SSL_METHOD *method, BIO *tracebi return ctx; } +#ifdef __linux__ +static std::string SSLLoadCertificateAuthorities(SSL_CTX* ctx) { + check(SSL_CTX_set_default_verify_paths(ctx) == 1); + return ""; +} +#else static std::string SSLLoadCertificateAuthorities(SSL_CTX* ctx) { HCERTSTORE hStore = CertOpenSystemStoreW(0, L"ROOT"); @@ -427,6 +459,7 @@ static std::string SSLLoadCertificateAuthorities(SSL_CTX* ctx) { CertCloseStore(hStore, 0); return ""; } +#endif static std::string SSLUseCertificateString(SSL_CTX* ctx, const char* str) { SSLClearErrors(); @@ -747,7 +780,7 @@ void FLpxChannel::Advance() AdvanceReadWrite(); break; default: - checkf(false, L"EChanState is invalid"); + checkf(false, TEXT("EChanState is invalid")); break; } diff --git a/Source/Integration/SampleActorComponent.cpp b/Source/Integration/SampleActorComponent.cpp new file mode 100644 index 00000000..b5142a32 --- /dev/null +++ b/Source/Integration/SampleActorComponent.cpp @@ -0,0 +1,34 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "SampleActorComponent.h" + +// Sets default values for this component's properties +USampleActorComponent::USampleActorComponent() +{ + // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features + // off to improve performance if you don't need them. + PrimaryComponentTick.bCanEverTick = true; + + // ... +} + + +// Called when the game starts +void USampleActorComponent::BeginPlay() +{ + Super::BeginPlay(); + + // ... + +} + + +// Called every frame +void USampleActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // ... +} + diff --git a/Source/Integration/SampleActorComponent.h b/Source/Integration/SampleActorComponent.h new file mode 100644 index 00000000..fe00ca06 --- /dev/null +++ b/Source/Integration/SampleActorComponent.h @@ -0,0 +1,28 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "SampleActorComponent.generated.h" + + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class INTEGRATION_API USampleActorComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + // Sets default values for this component's properties + USampleActorComponent(); + +protected: + // Called when the game starts + virtual void BeginPlay() override; + +public: + // Called every frame + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + + +}; diff --git a/Source/Integration/StringDecoder.cpp b/Source/Integration/StringDecoder.cpp index 1182914d..a25b4a7a 100644 --- a/Source/Integration/StringDecoder.cpp +++ b/Source/Integration/StringDecoder.cpp @@ -1,26 +1,2 @@ #include "StringDecoder.h" -FlxStringDecoder::FlxStringDecoder(std::string_view s) { - Text = s.data(); - Size = s.size(); - ErrBeyondEOF = false; - ErrStringTooLong = false; -} - -std::string_view FlxStringDecoder::read_string_view() { - size_t length = read_length(); - if (length > Size) { - ErrBeyondEOF = true; - return std::string_view(); - } - std::string_view result(Text, length); - Text += length; - Size -= length; - return result; -} - -void FlxStringDecoder::set_at_eof() { - Text += Size; - Size = 0; - -} \ No newline at end of file diff --git a/Source/Integration/StringDecoder.h b/Source/Integration/StringDecoder.h index e585e741..6ddec598 100644 --- a/Source/Integration/StringDecoder.h +++ b/Source/Integration/StringDecoder.h @@ -1,116 +1,46 @@ #pragma once #include "CoreMinimal.h" -#include "lpx-basewriter.hpp" - -///////////////////////////////////////////////////// -// -// FlxStringDecoder -// -// This class is used to decipher 8-bit strings that -// contain packed ints, strings, and other data. -// The typical example of usage is to decipher the -// serialized animation queues fed to us by Luprex. -// -// The FlxStringDecoder doesn't make a copy of the string -// you pass in, instead, it stores a pointer to it. -// So be sure not to free the string until you're -// done analyzing it. -// -// You'll note that some of the function names are -// lowercase, with underscores. That's because they're -// inherited from Luprex classes, and luprex classes -// use that naming convention. There's not any easy -// workaround for that. -// -///////////////////////////////////////////////////// +#include "lpx-basebuffer.hpp" -class FlxStringDecoder : public BaseReader { +using FlxSimpleDynamic = SimpleDynamic; + +class FlxStreamBufferCore { private: - const char* Text; - size_t Size; + bool err_eof_on_read_; + bool err_string_too_long_; + bool err_integer_truncated_; +protected: -public: - // You can check and clear these error flags at will. - // - bool ErrBeyondEOF; - bool ErrStringTooLong; - -public: - // This function is required by BaseReader. - // It's not meant for end users. - // - void read_bytes_into(char* buffer, size_t size) { - if (size > Size) { - memset(buffer, 0, size); - ErrBeyondEOF = true; - set_at_eof(); - } - else { - memcpy(buffer, Text, size); - Text += size; - Size -= size; - } - } - - // This function is required by BaseReader. - // It's not meant for end users. - // - void raise_string_too_long() { - ErrStringTooLong = true; - } - -public: - // The type returned by read_string. - // - using read_string_type = std::string; - - // Initialize the string decoder with a text to analyze. - // - FlxStringDecoder(std::string_view s); - - // Get the size of the remaining text. - // - size_t get_size() { return Size; } + void *basebuffer_malloc(size_t size) { return malloc(size); } + void basebuffer_free(void *p) { free(p); } + void clear_error_flags() { err_eof_on_read_ = err_string_too_long_ = err_integer_truncated_ = false; } + void raise_eof_on_read() { err_eof_on_read_ = true; } + void raise_string_too_long() { err_string_too_long_ = true; } + void raise_integer_truncated() { err_integer_truncated_ = true; } - // Return true if the remaining text is empty. - // - bool at_eof() { return Size == 0; } - - // Move the cursor to EOF. - // - void set_at_eof(); - - // Read a string as a string_view - // - // This reads a string from the source, returning - // it as a string_view that points into the buffer. - // Naturally, if you release the buffer, the - // string_view is invalidated. - // - // This is considerably faster than read_string. - // - std::string_view read_string_view(); - - // Inherited Methods: - // - // The following methods are inherited from BaseReader: - // - // uint8_t read_uint8(); - // uint16_t read_uint16(); - // uint32_t read_uint32(); - // uint64_t read_uint64(); - // int8_t read_int8(); - // int16_t read_int16(); - // int32_t read_int32(); - // int64_t read_int64(); - // bool read_bool(); - // char read_char(); - // float read_float(); - // double read_double(); - // size_t read_length(); - // std::string read_string_limit(uint64_t size); - // std::string read_string(); - // + bool get_err_eof_on_read() const { return err_eof_on_read_; } + bool get_err_string_too_long() const { return err_string_too_long_; } + bool get_err_integer_truncated() const { return err_integer_truncated_; } + bool any_error() const { return err_eof_on_read_ || err_string_too_long_ || err_integer_truncated_; } }; + +class FlxStreamBuffer : public BaseBuffer { +public: + using BaseBuffer::BaseBuffer; + + void write_fvector(const FVector &xyz) { + write_double(xyz.X); + write_double(xyz.Y); + write_double(xyz.Z); + } + + FVector read_fvector() { + double x = read_double(); + double y = read_double(); + double z = read_double(); + return FVector(x, y, z); + } +}; + diff --git a/Source/Integration/Tangible.cpp b/Source/Integration/Tangible.cpp index 094c20c7..259776a2 100644 --- a/Source/Integration/Tangible.cpp +++ b/Source/Integration/Tangible.cpp @@ -2,4 +2,132 @@ #include "Tangible.h" +#include "TangibleManager.h" + +UlxTangible::UlxTangible() +{ + Manager = nullptr; + TangibleId = -1; + CurrentActor = nullptr; + ActorBlueprint = nullptr; + NearAccordingToLuprex = false; + NearAccordingToUnreal = false; +} + +void UlxTangible::Init(UlxTangibleManager* tm, int64 id) +{ + Manager = tm; + TangibleId = id; +} + +bool UlxTangible::BlueprintIsTangible(TSubclassOf bp) { + if (bp == nullptr) return true; + return bp->ImplementsInterface(UlxTangibleInterface::StaticClass()); +} + +void UlxTangible::SetActorBlueprintClass(TSubclassOf bp) { + // If we're already of the right class, do nothing. + if (ActorBlueprint == bp) { + return; + } + + // Sanity check the blueprint. Nullptr is allowed. + check(BlueprintIsTangible(bp)); + + // If there's already an actor, delete it. + if (CurrentActor != nullptr) { + // Remove the tangible component. This is probably + // unnecessary, but it makes it more likely that we'll + // catch bugs early. + UlxTangibleComponent* comp = CurrentActor->GetComponentByClass(); + if (comp != nullptr) { + comp->DestroyComponent(); + } + + // Now destroy the actor itself. According to various + // documents I've read online, it may be necessary to take + // further steps to delete the object. Not clear. + CurrentActor->Destroy(); + } + + // Update the blueprint reference. + ActorBlueprint = bp; + + // Now create a new actor, unless the BP is nullptr. + if (ActorBlueprint != nullptr) { + // Create the actor. + FActorSpawnParameters params; + FVector location(0, 0, 0); + FRotator rotation(0, 0, 0); + UWorld* w = Manager->GetWorld(); + AActor* a = w->SpawnActor(ActorBlueprint, &location, &rotation, params); + + // Insert a TangibleComponent into the actor. + UActorComponent* ac = a->AddComponentByClass(UlxTangibleComponent::StaticClass(), false, FTransform::Identity, false); + UlxTangibleComponent* tc = Cast(ac); + check(tc != nullptr); + + // Make the tangible point to the actor and vice versa. + tc->Tangible = this; + CurrentActor = a; + } +} + +void UlxTangible::UpdateAnimationQueue(std::string_view aq) { + AnimTracker.Update(aq); + int limit = 3; + while (AnimTracker.IsChanged()) { + if (limit == 0) break; + limit -= 1; + AnimTracker.ClearChanged(); + IlxTangibleInterface::Execute_AnimationStateChanged(GetActor()); + } +} + +FVector UlxTangible::GetLocation() const { + if (CurrentActor == nullptr) { + return FVector(0,0,0); + } else { + return CurrentActor->GetActorLocation(); + } +} + +void UlxTangible::Destroy() { + SetActorBlueprintClass(nullptr); + Manager = nullptr; + TangibleId = -1; + CurrentActor = nullptr; + ActorBlueprint = nullptr; + AnimTracker.Clear(); + NearAccordingToLuprex = false; + NearAccordingToUnreal = false; +} + +static UlxTangible *GetActorTangible(AActor *actor) { + UlxTangibleComponent* comp = actor->GetComponentByClass(); + check(comp != nullptr); + UlxTangible *result = comp->Tangible.Get(); + check(result != nullptr); + return result; +} + +void UlxTangible::GetCurrentAnimation(AActor *target, FlxAnimationStep &step) { + step = GetActorTangible(target)->AnimTracker.GetCurrentAnimation(); +} + +void UlxTangible::FinishedAnimation(AActor *target, const FlxAnimationStep &step, bool autoxyz, bool autofacing, bool autoplane) { + UlxTangible *tan = GetActorTangible(target); + if (autoxyz) step.AutoUpdateXYZ(target); + if (autofacing) step.AutoUpdateFacing(target); + if (autoplane) step.AutoUpdatePlane(&(tan->Plane)); + tan->AnimTracker.FinishedAnimation(step.Hash); +} + +FString UlxTangible::GetTangiblePlane(AActor* target) { + return GetActorTangible(target)->Plane.ToString(); +} + +void UlxTangible::SetTangiblePlane(AActor* target, const FString& plane) { + GetActorTangible(target)->Plane = FName(plane); +} diff --git a/Source/Integration/Tangible.h b/Source/Integration/Tangible.h index 84f0686f..4545a33b 100644 --- a/Source/Integration/Tangible.h +++ b/Source/Integration/Tangible.h @@ -3,27 +3,203 @@ #pragma once #include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" +#include "Components/ActorComponent.h" #include "AnimQueue.h" -#include "TangibleInterface.h" #include "Tangible.generated.h" + +class UlxTangibleManager; + +// This class does not need to be modified. +UINTERFACE(Blueprintable) +class UlxTangibleInterface : public UInterface +{ + GENERATED_BODY() +}; + /** + * + * IlxTangibleInterface * + * This class implements the interface that an Actor must implement + * in order for that Actor to be usable as a Tangible. + * */ + +class INTEGRATION_API IlxTangibleInterface +{ + GENERATED_BODY() + + // Add interface functions to this class. This is the class that will be inherited to implement this interface. +public: + UFUNCTION(BlueprintImplementableEvent, Category = "Tangible Functionality") + bool AnimationStateChanged(); +}; + + +/** + * + * UlxTangible + * + * The Tangible stores all the data we need for a tangible, + * such as the animation queue and so forth. + * + * From time to time, a tangible can change its blueprint class. + * To do that, we have to delete and recreate the actor. This + * is all set up so that it is possible to do that. + * + * The tangible has a place to store an Actor pointer. This + * actor pointer is allowed to be nullptr, especially in the + * case that the blueprint hasn't been set yet. + * + * This also serves as a repository for blueprint functions + * that operate on tangible actors. + * + */ + UCLASS() class INTEGRATION_API UlxTangible : public UObject -{ +{ GENERATED_BODY() public: - UPROPERTY() - AActor* Actor; + UlxTangible(); + // My Tangible Manager. + UPROPERTY() + TObjectPtr Manager; + + // The tangible ID. + UPROPERTY() + uint64 TangibleId; + + UPROPERTY() + TWeakObjectPtr CurrentActor; + + // The blueprint class of the actor. + UPROPERTY() + TSubclassOf ActorBlueprint; + + // Animation tracker FlxAnimTracker AnimTracker; - void Init(AActor* a) { - Actor = a; - } + // Current Plane. + FName Plane; + + // True if luprex thinks this object is Near the player. + bool NearAccordingToLuprex; + + // True if unreal thinks this object is Near the player. + bool NearAccordingToUnreal; + +public: + // Initialize a new tangible. + // + // This links the tangible to its TangibleManager and + // sets the tangible's ID. + // + void Init(UlxTangibleManager *tm, int64 id); + + // Destroy a tangible. + // + // Delete the actor associated with the tangible, if any, + // and clean up everything else. + // + void Destroy(); + + // Get the actor associated with this tangible. + // + // Note that this may return nullptr: it is valid for a + // tangible to have no actor associated with it. This + // is most commonly the case if the blueprint class of + // the tangible is set to nullptr. + // + // Also bear in mind that if a tangible changes its blueprint + // class, then the actor will have to be deleted and + // recreated. In that case, GetActor will return a different + // pointer than before the blueprint change. + // + AActor* GetActor() const { return CurrentActor.Get(); } + + // Get the location of the tangible. + // + // Note that if the actor is nullptr, the location is always + // at the origin. + // + FVector GetLocation() const; + + // Check a blueprint class to see if it is valid as a Tangible. + // + // In order for a blueprint class to be used as a tangible, + // it must implement the interface IlxTangibleInterface. + // This function also returns true for nullptr. + // + static bool BlueprintIsTangible(TSubclassOf bp); + + // Change the blueprint class of the tangible. + // + // This requires the deletion and recreation of the Actor. + // The blueprint class must satisfy 'BlueprintIsTangible' above. + // + // It is legal to pass in nullptr for the blueprint class. + // Whenever the blueprint class is nullptr, the Actor is + // also nullptr. Ie, a tangible will have no Actor associated + // with it if its blueprint class is nullptr. + // + void SetActorBlueprintClass(TSubclassOf bp); + + // Update the animation queue from Luprex. + // + // This reads the animation queue, and then based on + // what is new in the animation queue, it calls into + // the Actor's TangibleInterface, calling methods such + // as 'StartAnimation' and 'AbortAnimation' as necessary. + // + // If the animation queue specifies a blueprint change, + // this function will eventually implement that by calling + // SetActorBlueprintClass above. This is not implemented + // yet. + // + void UpdateAnimationQueue(std::string_view aq); + +public: + UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = Luprex) + static void GetCurrentAnimation(AActor *target, FlxAnimationStep &step); + + UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = Luprex) + static void FinishedAnimation(AActor *target, const FlxAnimationStep &step, + bool AutoUpdateXYZ = true, bool AutoUpdateFacing = true, bool AutoUpdatePlane = true); + + UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = Luprex) + static FString GetTangiblePlane(AActor* target); + + UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = Luprex) + static void SetTangiblePlane(AActor* target, const FString& plane); }; + +/** + * + * UlxTangibleComponent + * + * The TangibleComponent holds a pointer to the Tangible. + * This is the only purpose it serves: to make it possible to + * have the Actor point to its corresponding Tangible. + * + * The TangibleComponent is procedurally inserted into the Actor. + * The TangibleComponent is not visible to blueprints. + * + */ + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class INTEGRATION_API UlxTangibleComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UlxTangibleComponent() : Tangible(nullptr) {} + + // The actor that we're a part of. + UPROPERTY() + TWeakObjectPtr Tangible; +}; diff --git a/Source/Integration/TangibleInterface.cpp b/Source/Integration/TangibleInterface.cpp deleted file mode 100644 index ae86e205..00000000 --- a/Source/Integration/TangibleInterface.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - - -#include "TangibleInterface.h" - -// Add default functionality here for any IlxTangibleInterface functions that are not pure virtual. diff --git a/Source/Integration/TangibleInterface.h b/Source/Integration/TangibleInterface.h deleted file mode 100644 index d25718ca..00000000 --- a/Source/Integration/TangibleInterface.h +++ /dev/null @@ -1,31 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Interface.h" -#include "AnimQueue.h" -#include "TangibleInterface.generated.h" - -// This class does not need to be modified. -UINTERFACE(Blueprintable) -class UlxTangibleInterface : public UInterface -{ - GENERATED_BODY() -}; - -/** - * - */ -class INTEGRATION_API IlxTangibleInterface -{ - GENERATED_BODY() - - // Add interface functions to this class. This is the class that will be inherited to implement this interface. -public: - UFUNCTION(BlueprintImplementableEvent, Category = "Luprex") - bool StartAnimation(ElxAnimationMode mode, const FlxAnimationStep& step); - - UFUNCTION(BlueprintImplementableEvent, Category = "Luprex") - bool AbortAnimation(int64 hash); -}; diff --git a/Source/Integration/TangibleManager.cpp b/Source/Integration/TangibleManager.cpp index 046d42c7..3bbeda20 100644 --- a/Source/Integration/TangibleManager.cpp +++ b/Source/Integration/TangibleManager.cpp @@ -2,27 +2,25 @@ #include "TangibleManager.h" -#include "TangibleInterface.h" +#include "Tangible.h" #include "DebugPrint.h" using namespace DebugPrint; +using TanArray = UlxTangibleManager::TanArray; +using IdArray = UlxTangibleManager::IdArray; -FTangibleManager::FTangibleManager() { +UlxTangibleManager::UlxTangibleManager() { World = nullptr; ClassTangibleActor = nullptr; - Actor = 0; - Near = IdView(); } -void FTangibleManager::Init(UWorld *world, UClass* tanact) { +void UlxTangibleManager::Init(UWorld* world, UClass* tanact) { World = world; ClassTangibleActor = tanact; - Actor = 0; - Near = IdView(); } -UlxTangible *FTangibleManager::GetTangible(int64 id) { - UlxTangible **p = IdToTangible.Find(id); +UlxTangible* UlxTangibleManager::GetTangible(int64 id) const { + UlxTangible*const* p = IdToTangible.Find(id); if (p == nullptr) { return nullptr; } else { @@ -30,31 +28,90 @@ UlxTangible *FTangibleManager::GetTangible(int64 id) { } } -UlxTangible *FTangibleManager::MakeTangible(int64 id) { - UlxTangible *& p = IdToTangible.FindOrAdd(id); - if (p == nullptr) { - FVector location(0,0,0); - FRotator rotation(0, 0, 0); - FActorSpawnParameters params; - AActor* a = World->SpawnActor(ClassTangibleActor, &location, &rotation, params); - check(a != nullptr); - check(a->GetClass()->ImplementsInterface(UlxTangibleInterface::StaticClass())); - p = NewObject(); - p->Init(a); +#pragma optimize("", off) +UlxTangible* UlxTangibleManager::MakeTangible(int64 id) { + UlxTangible*& t = IdToTangible.FindOrAdd(id); + if (t == nullptr) { + t = NewObject(); + t->Init(this, id); + + // TODO: fix this. The blueprint needs to be assigned + // during animation queue parsing, based on the animation + // queue keyword 'bp'. + t->SetActorBlueprintClass(ClassTangibleActor); } - return p; + return t; } -void FTangibleManager::DeleteTangible(int64 id) { +void UlxTangibleManager::DeleteTangible(int64 id) { // IMPLEMENT ME } -FTangibleManager::IdArray FTangibleManager::GetLive() { - IdArray result; +TanArray UlxTangibleManager::GetAllTangibles() const { + TanArray result; result.SetNum(IdToTangible.Num()); int next = 0; - for (auto &pair : IdToTangible) { - result[next++] = pair.Key; + for (auto& pair : IdToTangible) { + result[next++] = pair.Value; } return result; -} \ No newline at end of file +} + +#pragma optimize("", off) +void UlxTangibleManager::UpdateNearAccordingToLuprex(IdView near) { + // Clear all the 'NearAccordingToLuprex' flags. + for (const auto& pair : IdToTangible) { + pair.Value->NearAccordingToLuprex = false; + } + // For every ID on the list, create it if it doesn't exist, + // mark it, and return it. + for (int64 id : near) { + UlxTangible* tan = MakeTangible(id); + tan->NearAccordingToLuprex = true; + } +} + +#pragma optimize("", off) +void UlxTangibleManager::RecalcNearAccordingToUnreal(int64 player, double radius) { + UlxTangible *p = GetTangible(player); + check (p != nullptr); + FVector playerpos = p->CurrentActor->GetActorLocation(); + FName playerplane = p->Plane; + double radiussq = radius * radius; + for (const auto& pair : IdToTangible) { + UlxTangible *tan = pair.Value; + if (tan->Plane != playerplane) { + tan->NearAccordingToUnreal = false; + } else { + FVector pos = tan->GetLocation(); + double distsq = FVector::DistSquared(pos, playerpos); + tan->NearAccordingToUnreal = (distsq <= radiussq); + } + } +} + +void UlxTangibleManager::DeleteFarawayTangibles() { + // Make a list of tangibles that need to be deleted. + TanArray faraway; + for (const auto& pair : IdToTangible) { + UlxTangible *tan = pair.Value; + if (!(tan->NearAccordingToLuprex || tan->NearAccordingToUnreal)) { + faraway.Add(tan); + } + } + + for (UlxTangible *tan : faraway) { + IdToTangible.Remove(tan->TangibleId); + tan->Destroy(); // Remove the actor from the scene. + } +} + +IdArray UlxTangibleManager::GetIds(const TanArray &arr) { + IdArray result; + result.SetNum(arr.Num()); + for (int i = 0; i < arr.Num(); i++) { + result[i] = arr[i]->TangibleId; + } + return result; +} + diff --git a/Source/Integration/TangibleManager.h b/Source/Integration/TangibleManager.h index 9a4e1a06..6d9c8827 100644 --- a/Source/Integration/TangibleManager.h +++ b/Source/Integration/TangibleManager.h @@ -8,62 +8,77 @@ #include "Tangible.h" #include "TangibleManager.generated.h" -USTRUCT() -struct INTEGRATION_API FTangibleManager +UCLASS() +class INTEGRATION_API UlxTangibleManager : public UObject { GENERATED_BODY() + public: - // Import these types into our Namespace. - using IdArray = CommonTypes::IdArray; + // Types used frequently. using IdView = CommonTypes::IdView; + using IdArray = CommonTypes::IdArray; + using TanArray = TArray; - // A pointer to the UWorld. - UWorld* World; + // A pointer to our world. + UPROPERTY() + TWeakObjectPtr World; - // A pointer to uclass TangibleActor. + // A pointer to uclass TangibleActor. This is the class + // of actors that we create (for now). UPROPERTY() TSubclassOf ClassTangibleActor; - // Given a tangible ID, look up actor pointer (or NULL if actor was deleted) + // Given a tangible ID, look up the TangibleComponent of that actor. UPROPERTY() TMap IdToTangible; - // Actor tangible Id. - int64 Actor; - - // Tangibles near the actor. - IdView Near; - public: - FTangibleManager(); + UlxTangibleManager(); // Initialize the tangible manager. // void Init(UWorld *world, UClass* tanact); + // Get a pointer to our world. + // + UWorld* GetWorld() const override { return World.Get(); } + // Get the tangible if it exists, otherwise return NULL - UlxTangible* GetTangible(int64 id); + // + UlxTangible* GetTangible(int64 id) const; // Get the tangible if it exists, otherwise create it. + // UlxTangible* MakeTangible(int64 id); // Delete the tangible. + // void DeleteTangible(int64 id); - // Get/Set the Id of the actor. - // - int64 GetActor() const { return Actor; }; - void SetActor(int64 id) { Actor = id; } + // Get an array of all tangibles. + // + TanArray GetAllTangibles() const; + + // Update the 'NearAccordingToLuprex' flags. + // + // Also creates stub tangibles for every Id in the list. + // + void UpdateNearAccordingToLuprex(IdView near); - // Get/Set the list of tangibles near the player, according to Luprex. + // Recalculate the 'NearAccordingToUnreal' flags. // - IdView GetNear() const { return Near; } - void SetNear(IdView near) { Near = near; } + void RecalcNearAccordingToUnreal(int64 player, double radius); + + // Delete Far Tangibles. + // + // Any tangible whose 'NearAccordingToLuprex' and 'NearAccordingToUnreal' + // flags are both false is deleted. You probably want to update both + // flags by calling the two routines above before calling this. + // + void DeleteFarawayTangibles(); - // Get the Live list. + // Given an array of tangibles, return an array of tangible Ids. // - // Efficiency note: this makes a copy of the array. - // - IdArray GetLive(); + static IdArray GetIds(const TanArray &tans); }; diff --git a/Source/Integration/lpx-basewriter.hpp b/Source/Integration/lpx-basewriter.hpp deleted file mode 100644 index 92f52af6..00000000 --- a/Source/Integration/lpx-basewriter.hpp +++ /dev/null @@ -1 +0,0 @@ -#include "c:/luprex/ext/base-writer.hpp" \ No newline at end of file diff --git a/Source/Integration/lpx-drvutil.cpp b/Source/Integration/lpx-drvutil.cpp deleted file mode 100644 index c88f328a..00000000 --- a/Source/Integration/lpx-drvutil.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "c:/luprex/cpp/drv/drvutil.cpp" \ No newline at end of file diff --git a/Source/Integration/lpx-drvutil.hpp b/Source/Integration/lpx-drvutil.hpp deleted file mode 100644 index f47576cb..00000000 --- a/Source/Integration/lpx-drvutil.hpp +++ /dev/null @@ -1 +0,0 @@ -#include "c:/luprex/cpp/drv/drvutil.hpp" \ No newline at end of file diff --git a/Source/Integration/lpx-enginewrapper.hpp b/Source/Integration/lpx-enginewrapper.hpp deleted file mode 100644 index 39a03edd..00000000 --- a/Source/Integration/lpx-enginewrapper.hpp +++ /dev/null @@ -1 +0,0 @@ -#include "c:/luprex/cpp/core/enginewrapper.hpp" \ No newline at end of file diff --git a/luprex-install.sh b/luprex-install.sh new file mode 100755 index 00000000..ca7a9832 --- /dev/null +++ b/luprex-install.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# +# +# Install symbolic links to the Luprex source and the Luprex DLL. +# +# These symbolic links are needed to be able to build integration. +# +# + +if [ -d c:/luprex ] ; then + LUPREX=c:/luprex + DLL=$LUPREX/build/visual/luprexlib.dll +else + LUPREX=$HOME/luprex + DLL=$LUPREX/build/linux/luprexlib.so +fi + +rm -f Source/Integration/lpx-*.hpp +rm -f Source/Integration/lpx-*.cpp + +echo '#include "'$LUPREX'/ext/base-buffer.hpp"' > Source/Integration/lpx-basebuffer.hpp +echo '#include "'$LUPREX'/cpp/drv/drvutil.hpp"' > Source/Integration/lpx-drvutil.hpp +echo '#include "'$LUPREX'/cpp/drv/drvutil.cpp"' > Source/Integration/lpx-drvutil.cpp +echo '#include "'$LUPREX'/cpp/core/enginewrapper.hpp"' > Source/Integration/lpx-enginewrapper.hpp +echo '#define LUPREX_DLL_PATH "'$DLL'"' > Source/Integration/lpx-paths.hpp +echo '#define LUPREX_ROOT_PATH "'$LUPREX'"' >> Source/Integration/lpx-paths.hpp +