From 77b4a4d402bbb6906e5b36ad21a2a991c4653ea4 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Wed, 11 Sep 2024 10:49:31 -0700 Subject: [PATCH] feat: ecsact package subsystems (#15) --- Source/Ecsact/Ecsact.Build.cs | 17 +- .../EcsactAsyncConnectBlueprintAction.cpp | 14 +- .../EcsactAsyncConnectBlueprintAction.h | 4 +- Source/Ecsact/Public/EcsactUnreal/Ecsact.cpp | 27 +- Source/Ecsact/Public/EcsactUnreal/Ecsact.h | 9 +- .../Public/EcsactUnreal/EcsactAsyncRunner.cpp | 77 ++- .../Public/EcsactUnreal/EcsactExecution.cpp | 2 +- .../Public/EcsactUnreal/EcsactExecution.h | 7 +- .../Public/EcsactUnreal/EcsactRunner.cpp | 199 ++++++- .../Ecsact/Public/EcsactUnreal/EcsactRunner.h | 112 +++- .../EcsactUnreal/EcsactRunnerSubsystem.cpp | 42 ++ .../EcsactUnreal/EcsactRunnerSubsystem.h | 57 ++ .../Public/EcsactUnreal/EcsactSettings.h | 3 + .../Public/EcsactUnreal/EcsactSyncRunner.cpp | 8 +- .../EcsactUnrealExecutionOptions.cpp | 71 ++- .../EcsactUnrealExecutionOptions.h | 62 +- .../EcsactUnrealCodegenPlugin.cpp | 539 +++++++++++++++++- 17 files changed, 1181 insertions(+), 69 deletions(-) create mode 100644 Source/Ecsact/Public/EcsactUnreal/EcsactRunnerSubsystem.cpp create mode 100644 Source/Ecsact/Public/EcsactUnreal/EcsactRunnerSubsystem.h diff --git a/Source/Ecsact/Ecsact.Build.cs b/Source/Ecsact/Ecsact.Build.cs index fbe1f16..4ee17f3 100644 --- a/Source/Ecsact/Ecsact.Build.cs +++ b/Source/Ecsact/Ecsact.Build.cs @@ -29,7 +29,6 @@ public Ecsact(ReadOnlyTargetRules Target) : base(Target) { "Engine", "Slate", "SlateCore", - "Kismet", }); DynamicallyLoadedModuleNames.AddRange(new string[] { @@ -80,14 +79,14 @@ public Ecsact(ReadOnlyTargetRules Target) : base(Target) { "--plugin=cpp_systems_source" }; - // if(!File.Exists(EcsactUnrealCodegenPluginPath)) { - // Console.WriteLine( - // "warning: EcsactUnrealCodegenPlugin was not built. It should have " - // + "been shipped with the Ecsact Unreal integration plugin." - // ); - // } else { - // CodegenArgs.Add($"--plugin={EcsactUnrealCodegenPluginPath}"); - // } + if(!File.Exists(EcsactUnrealCodegenPluginPath)) { + Console.WriteLine( + "warning: EcsactUnrealCodegenPlugin was not built. It should have " + + "been shipped with the Ecsact Unreal integration plugin." + ); + } else { + CodegenArgs.Add($"--plugin={EcsactUnrealCodegenPluginPath}"); + } CodegenArgs.AddRange(EcsactSources); ExecEcsactCli(CodegenArgs); diff --git a/Source/Ecsact/Public/EcsactUnreal/Blueprint/EcsactAsyncConnectBlueprintAction.cpp b/Source/Ecsact/Public/EcsactUnreal/Blueprint/EcsactAsyncConnectBlueprintAction.cpp index f917fad..0001d14 100644 --- a/Source/Ecsact/Public/EcsactUnreal/Blueprint/EcsactAsyncConnectBlueprintAction.cpp +++ b/Source/Ecsact/Public/EcsactUnreal/Blueprint/EcsactAsyncConnectBlueprintAction.cpp @@ -23,10 +23,20 @@ auto UEcsactAsyncConnectBlueprintAction::Activate() -> void { TEXT("Cannot use Ecsact async blueprint api with runner that does not " "implement IEcsactAsyncRunnerEvents") ); + OnError.Broadcast(EAsyncConnectError::AsyncRunnerEventsUnavailable); + OnDone.Broadcast({}); return; } auto req_id = ecsact_async_connect(Utf8ConnectionString.c_str()); + UE_LOG(Ecsact, Warning, TEXT("async connect request id=%i"), req_id); + + if(req_id == ECSACT_INVALID_ID(async_request)) { + UE_LOG(Ecsact, Error, TEXT("Invalid Request ID")); + OnError.Broadcast(EAsyncConnectError::InvalidRequestId); + OnDone.Broadcast({}); + return; + } async_events->OnRequestDone( req_id, @@ -46,6 +56,7 @@ auto UEcsactAsyncConnectBlueprintAction::Activate() -> void { } auto UEcsactAsyncConnectBlueprintAction::OnRequestDone() -> void { + UE_LOG(Ecsact, Error, TEXT("OnRequestDone??")); if(!bConnectFailed) { OnSuccess.Broadcast({}); } @@ -55,12 +66,13 @@ auto UEcsactAsyncConnectBlueprintAction::OnRequestDone() -> void { auto UEcsactAsyncConnectBlueprintAction::OnRequestError( // ecsact_async_error Error ) -> void { + UE_LOG(Ecsact, Error, TEXT("OnRequestError??")); switch(Error) { case ECSACT_ASYNC_ERR_PERMISSION_DENIED: OnError.Broadcast(EAsyncConnectError::PermissionDenied); break; case ECSACT_ASYNC_INVALID_CONNECTION_STRING: - OnError.Broadcast(EAsyncConnectError::PermissionDenied); + OnError.Broadcast(EAsyncConnectError::InvalidConnectionString); break; } diff --git a/Source/Ecsact/Public/EcsactUnreal/Blueprint/EcsactAsyncConnectBlueprintAction.h b/Source/Ecsact/Public/EcsactUnreal/Blueprint/EcsactAsyncConnectBlueprintAction.h index 50c01dc..30a7cd4 100644 --- a/Source/Ecsact/Public/EcsactUnreal/Blueprint/EcsactAsyncConnectBlueprintAction.h +++ b/Source/Ecsact/Public/EcsactUnreal/Blueprint/EcsactAsyncConnectBlueprintAction.h @@ -8,6 +8,8 @@ UENUM() enum class EAsyncConnectError : uint8 { NoError, + AsyncRunnerEventsUnavailable, + InvalidRequestId, PermissionDenied, InvalidConnectionString, }; @@ -54,7 +56,7 @@ class ECSACT_API UEcsactAsyncConnectBlueprintAction FAsyncConnectDoneCallback OnDone; /** - * Async request is done and no errors occured. + * Async request is done and no errors occurred. */ UPROPERTY(BlueprintAssignable) FAsyncConnectDoneCallback OnSuccess; diff --git a/Source/Ecsact/Public/EcsactUnreal/Ecsact.cpp b/Source/Ecsact/Public/EcsactUnreal/Ecsact.cpp index 6e22de0..085db05 100644 --- a/Source/Ecsact/Public/EcsactUnreal/Ecsact.cpp +++ b/Source/Ecsact/Public/EcsactUnreal/Ecsact.cpp @@ -18,8 +18,12 @@ FOR_EACH_ECSACT_API_FN(INIT_ECSACT_API_FN, UNUSED_PARAM); FEcsactModule* FEcsactModule::Self = nullptr; auto FEcsactModule::Get() -> FEcsactModule& { - check(Self != nullptr); - return *Self; + if(GIsEditor) { + return FModuleManager::Get().GetModuleChecked("Ecsact"); + } else { + check(Self != nullptr); + return *Self; + } } auto FEcsactModule::Abort() -> void { @@ -84,13 +88,14 @@ auto FEcsactModule::UnloadEcsactRuntime() -> void { } auto FEcsactModule::StartupModule() -> void { + UE_LOG(Ecsact, Warning, TEXT("Ecsact Startup Module")); Self = this; if(!GIsEditor) { LoadEcsactRuntime(); } #if WITH_EDITOR FEditorDelegates::PreBeginPIE.AddRaw(this, &FEcsactModule::OnPreBeginPIE); - FEditorDelegates::EndPIE.AddRaw(this, &FEcsactModule::OnEndPIE); + FEditorDelegates::EndPIE.AddRaw(this, &FEcsactModule::OnPrePIEEnded); #endif } @@ -103,6 +108,7 @@ auto FEcsactModule::ShutdownModule() -> void { FEditorDelegates::PreBeginPIE.RemoveAll(this); FEditorDelegates::EndPIE.RemoveAll(this); #endif + UE_LOG(Ecsact, Warning, TEXT("Ecsact Shutdown Module")); Self = nullptr; } @@ -110,7 +116,7 @@ auto FEcsactModule::OnPreBeginPIE(bool _) -> void { LoadEcsactRuntime(); } -auto FEcsactModule::OnEndPIE(bool _) -> void { +auto FEcsactModule::OnPrePIEEnded(bool _) -> void { UnloadEcsactRuntime(); } @@ -149,17 +155,26 @@ auto FEcsactModule::StartRunner() -> void { UE_LOG( Ecsact, Log, - TEXT("Using ecsact runner: %s"), + TEXT("Starting ecsact runner: %s"), *Runner->GetClass()->GetName() ); Runner->AddToRoot(); + Runner->Start(); } } auto FEcsactModule::StopRunner() -> void { if(Runner != nullptr) { + UE_LOG( + Ecsact, + Log, + TEXT("Stopping ecsact runner: %s"), + *Runner->GetClass()->GetName() + ); + Runner->Stop(); Runner->RemoveFromRoot(); - Runner = nullptr; + Runner->MarkAsGarbage(); + Runner.Reset(); } } diff --git a/Source/Ecsact/Public/EcsactUnreal/Ecsact.h b/Source/Ecsact/Public/EcsactUnreal/Ecsact.h index 9a36bf6..fa29d85 100644 --- a/Source/Ecsact/Public/EcsactUnreal/Ecsact.h +++ b/Source/Ecsact/Public/EcsactUnreal/Ecsact.h @@ -2,21 +2,22 @@ #include "CoreMinimal.h" #include "Modules/ModuleManager.h" +#include "UObject/WeakObjectPtr.h" DECLARE_LOG_CATEGORY_EXTERN(Ecsact, Log, All); class FEcsactModule : public IModuleInterface { friend class EcsactUnrealExecution; - static FEcsactModule* Self; - void* EcsactRuntimeHandle; - class UEcsactRunner* Runner; + static FEcsactModule* Self; + void* EcsactRuntimeHandle; + TWeakObjectPtr Runner; auto LoadEcsactRuntime() -> void; auto UnloadEcsactRuntime() -> void; auto Abort() -> void; auto OnPreBeginPIE(bool bIsSimulating) -> void; - auto OnEndPIE(const bool bIsSimulating) -> void; + auto OnPrePIEEnded(const bool bIsSimulating) -> void; auto StartRunner() -> void; auto StopRunner() -> void; diff --git a/Source/Ecsact/Public/EcsactUnreal/EcsactAsyncRunner.cpp b/Source/Ecsact/Public/EcsactUnreal/EcsactAsyncRunner.cpp index dd2b758..6aa843f 100644 --- a/Source/Ecsact/Public/EcsactUnreal/EcsactAsyncRunner.cpp +++ b/Source/Ecsact/Public/EcsactUnreal/EcsactAsyncRunner.cpp @@ -28,10 +28,27 @@ auto UEcsactAsyncRunner::OnAsyncErrorRaw( for(auto req_id : request_ids) { auto cbs = self->RequestErrorCallbacks.Find(req_id); - if(cbs) { + + UE_LOG(Ecsact, Warning, TEXT("async request error id=%i"), req_id); + + if(cbs && !cbs->IsEmpty()) { for(auto& cb : *cbs) { - cb.ExecuteIfBound(async_err); + if(!cb.ExecuteIfBound(async_err)) { + UE_LOG( + Ecsact, + Warning, + TEXT("Unbound async error callback for request %i"), + req_id + ); + } } + } else { + UE_LOG( + Ecsact, + Warning, + TEXT("No async error callbacks for request %i"), + req_id + ); } } } @@ -54,27 +71,46 @@ auto UEcsactAsyncRunner::OnAsyncRequestDoneRaw( for(auto req_id : request_ids) { auto cbs = self->RequestDoneCallbacks.Find(req_id); - if(cbs) { + + UE_LOG(Ecsact, Warning, TEXT("async request done id=%i"), req_id); + + if(cbs && !cbs->IsEmpty()) { for(auto& cb : *cbs) { - cb.ExecuteIfBound(); + if(!cb.ExecuteIfBound()) { + UE_LOG( + Ecsact, + Warning, + TEXT("Unbound async done callback for request %i (self=%i)"), + req_id, + (intptr_t)self + ); + } } cbs->Empty(); + } else { + UE_LOG( + Ecsact, + Warning, + TEXT("No async done callbacks for request %i (self=%i)"), + req_id, + (intptr_t)self + ); } } } auto UEcsactAsyncRunner::Tick(float DeltaTime) -> void { + if(IsStopped()) { + return; + } + EnqueueExecutionOptions(); if(ecsact_async_flush_events == nullptr) { UE_LOG(Ecsact, Error, TEXT("ecsact_async_flush_events is unavailable")); } else { - ecsact_execution_events_collector* evc_c = nullptr; - if(EventsCollector != nullptr) { - evc_c = EventsCollector->GetCEVC(); - } - ecsact_async_flush_events(evc_c, &async_evc); + ecsact_async_flush_events(GetEventsCollector(), &async_evc); } } @@ -92,7 +128,14 @@ auto UEcsactAsyncRunner::EnqueueExecutionOptions() -> void { } if(ExecutionOptions->IsNotEmpty()) { - ecsact_async_enqueue_execution_options(*ExecutionOptions->GetCPtr()); + auto req_id = + ecsact_async_enqueue_execution_options(*ExecutionOptions->GetCPtr()); + UE_LOG( + Ecsact, + Warning, + TEXT("Actually enqueueing some options! (req_id=%i)"), + (int)req_id + ); ExecutionOptions->Clear(); } } @@ -109,6 +152,13 @@ auto UEcsactAsyncRunner::OnRequestDone( FAsyncRequestDoneCallback Callback ) -> void { check(RequestId != ECSACT_INVALID_ID(async_request)); + UE_LOG( + Ecsact, + Error, + TEXT("Adding on request done callback (req_id=%i self=%i)"), + RequestId, + (intptr_t)this + ); RequestDoneCallbacks.FindOrAdd(RequestId).Add(Callback); } @@ -118,6 +168,13 @@ auto UEcsactAsyncRunner::OnRequestError( FAsyncRequestErrorCallback Callback ) -> void { check(RequestId != ECSACT_INVALID_ID(async_request)); + UE_LOG( + Ecsact, + Error, + TEXT("Adding on request error callback (req_id=%i self=%i)"), + RequestId, + (intptr_t)this + ); RequestErrorCallbacks.FindOrAdd(RequestId).Add(Callback); } diff --git a/Source/Ecsact/Public/EcsactUnreal/EcsactExecution.cpp b/Source/Ecsact/Public/EcsactUnreal/EcsactExecution.cpp index e1a477c..6d8c403 100644 --- a/Source/Ecsact/Public/EcsactUnreal/EcsactExecution.cpp +++ b/Source/Ecsact/Public/EcsactUnreal/EcsactExecution.cpp @@ -7,6 +7,6 @@ auto EcsactUnrealExecution::DeltaTime() -> float { return DeltaTime_; } -auto EcsactUnrealExecution::Runner() -> class UEcsactRunner* { +auto EcsactUnrealExecution::Runner() -> TWeakObjectPtr { return FEcsactModule::Get().Runner; } diff --git a/Source/Ecsact/Public/EcsactUnreal/EcsactExecution.h b/Source/Ecsact/Public/EcsactUnreal/EcsactExecution.h index f9aed9a..26f3162 100644 --- a/Source/Ecsact/Public/EcsactUnreal/EcsactExecution.h +++ b/Source/Ecsact/Public/EcsactUnreal/EcsactExecution.h @@ -1,6 +1,9 @@ #pragma once -class EcsactUnrealExecution { +#include "EcsactUnreal/Ecsact.h" +#include "UObject/WeakObjectPtrTemplates.h" + +class ECSACT_API EcsactUnrealExecution { friend class UEcsactSyncRunner; friend class UEcsactAsyncRunner; static float DeltaTime_; @@ -18,5 +21,5 @@ class EcsactUnrealExecution { /** * */ - static auto Runner() -> class UEcsactRunner*; + static auto Runner() -> TWeakObjectPtr; }; diff --git a/Source/Ecsact/Public/EcsactUnreal/EcsactRunner.cpp b/Source/Ecsact/Public/EcsactUnreal/EcsactRunner.cpp index 5eb1f9d..f70890d 100644 --- a/Source/Ecsact/Public/EcsactUnreal/EcsactRunner.cpp +++ b/Source/Ecsact/Public/EcsactUnreal/EcsactRunner.cpp @@ -1,4 +1,42 @@ #include "EcsactUnreal/EcsactRunner.h" +#include "EcsactUnreal/EcsactUnrealExecutionOptions.h" +#include "UObject/ObjectMacros.h" +#include "UObject/UObjectIterator.h" +#include "EcsactUnreal/EcsactRunnerSubsystem.h" +#include "EcsactUnreal/Ecsact.h" +#include "ecsact/runtime/common.h" + +UEcsactRunner::UEcsactRunner() : EventsCollector{} { + ExecutionOptions = CreateDefaultSubobject( // + TEXT("ExecutionOptions") + ); + + EventsCollector.init_callback_user_data = this; + EventsCollector.update_callback_user_data = this; + EventsCollector.remove_callback_user_data = this; + EventsCollector.entity_created_callback_user_data = this; + EventsCollector.entity_destroyed_callback_user_data = this; + + EventsCollector.init_callback = ThisClass::OnInitComponentRaw; + EventsCollector.update_callback = ThisClass::OnUpdateComponentRaw; + EventsCollector.remove_callback = ThisClass::OnRemoveComponentRaw; + EventsCollector.entity_created_callback = ThisClass::OnEntityCreatedRaw; + EventsCollector.entity_destroyed_callback = ThisClass::OnEntityDestroyedRaw; +} + +auto UEcsactRunner::Start() -> void { + bIsStopped = false; + InitRunnerSubsystems(); +} + +auto UEcsactRunner::Stop() -> void { + ShutdownRunnerSubsystems(); + bIsStopped = true; +} + +auto UEcsactRunner::IsStopped() const -> bool { + return bIsStopped; +} auto UEcsactRunner::Tick(float DeltaTime) -> void { } @@ -11,5 +49,164 @@ auto UEcsactRunner::GetStatId() const -> TStatId { } auto UEcsactRunner::IsTickable() const -> bool { - return !IsTemplate(); + return !IsTemplate() && !bIsStopped; +} + +auto UEcsactRunner::CreateEntity() -> EcsactRunnerCreateEntityBuilder { + return {this, GeneratePlaceholderId()}; +} + +auto UEcsactRunner::GeneratePlaceholderId() -> ecsact_placeholder_entity_id { + static ecsact_placeholder_entity_id LastPlaceholderId = {}; + using ref_t = std::add_lvalue_reference_t< + std::underlying_type_t>; + reinterpret_cast(LastPlaceholderId) += 1; + return LastPlaceholderId; +} + +auto UEcsactRunner::InitRunnerSubsystems() -> void { + auto subsystem_types = TArray{}; + for(auto it = TObjectIterator{}; it; ++it) { + auto uclass = *it; + + if(!uclass->IsChildOf(UEcsactRunnerSubsystem::StaticClass())) { + continue; + } + + if(uclass->HasAnyClassFlags(CLASS_Abstract)) { + continue; + } + + if(uclass->HasAnyFlags(RF_Transient)) { + continue; + } + + subsystem_types.Add(uclass); + } + + check(RunnerSubsystems.IsEmpty()); + RunnerSubsystems.Empty(); + + for(auto t : subsystem_types) { + UE_LOG(Ecsact, Log, TEXT("Starting ecsact subsystem %s"), *t->GetName()); + auto subsystem = NewObject(this, t); + subsystem->AddToRoot(); + RunnerSubsystems.Add(subsystem); + } + + for(auto subsystem : RunnerSubsystems) { + subsystem->RunnerStart(this); + } +} + +auto UEcsactRunner::ShutdownRunnerSubsystems() -> void { + for(auto subsystem : RunnerSubsystems) { + if(subsystem == nullptr) { + continue; + } + subsystem->RunnerStop(this); + } + + RunnerSubsystems.Empty(); +} + +auto UEcsactRunner::GetEventsCollector() -> ecsact_execution_events_collector* { + return &EventsCollector; +} + +auto UEcsactRunner::OnInitComponentRaw( + ecsact_event event, + ecsact_entity_id entity_id, + ecsact_component_id component_id, + const void* component_data, + void* callback_user_data +) -> void { + auto self = static_cast(callback_user_data); + for(auto subsystem : self->RunnerSubsystems) { + subsystem->InitComponentRaw(entity_id, component_id, component_data); + } +} + +auto UEcsactRunner::OnUpdateComponentRaw( + ecsact_event event, + ecsact_entity_id entity_id, + ecsact_component_id component_id, + const void* component_data, + void* callback_user_data +) -> void { + auto self = static_cast(callback_user_data); + for(auto subsystem : self->RunnerSubsystems) { + subsystem->UpdateComponentRaw(entity_id, component_id, component_data); + } +} + +auto UEcsactRunner::OnRemoveComponentRaw( + ecsact_event event, + ecsact_entity_id entity_id, + ecsact_component_id component_id, + const void* component_data, + void* callback_user_data +) -> void { + auto self = static_cast(callback_user_data); + for(auto subsystem : self->RunnerSubsystems) { + subsystem->RemoveComponentRaw(entity_id, component_id, component_data); + } +} + +auto UEcsactRunner::OnEntityCreatedRaw( + ecsact_event event, + ecsact_entity_id entity_id, + ecsact_placeholder_entity_id placeholder_entity_id, + void* callback_user_data +) -> void { + auto self = static_cast(callback_user_data); + + auto create_callback = + self->CreateEntityCallbacks.Find(placeholder_entity_id); + if(create_callback) { + create_callback->Execute(entity_id); + self->CreateEntityCallbacks.Remove(placeholder_entity_id); + } + + for(auto subsystem : self->RunnerSubsystems) { + subsystem->EntityCreated(static_cast(entity_id)); + } +} + +auto UEcsactRunner::OnEntityDestroyedRaw( + ecsact_event event, + ecsact_entity_id entity_id, + ecsact_placeholder_entity_id placeholder_entity_id, + void* callback_user_data +) -> void { + auto self = static_cast(callback_user_data); + for(auto subsystem : self->RunnerSubsystems) { + subsystem->EntityDestroyed(static_cast(entity_id)); + } +} + +UEcsactRunner::EcsactRunnerCreateEntityBuilder::EcsactRunnerCreateEntityBuilder( + UEcsactRunner* Owner, + ecsact_placeholder_entity_id PlaceholderId +) + : Owner{Owner} + , PlaceholderId{PlaceholderId} + , Builder{Owner->ExecutionOptions->CreateEntity(PlaceholderId)} { +} + +UEcsactRunner::EcsactRunnerCreateEntityBuilder:: + EcsactRunnerCreateEntityBuilder(EcsactRunnerCreateEntityBuilder&&) = default; + +UEcsactRunner::EcsactRunnerCreateEntityBuilder:: + ~EcsactRunnerCreateEntityBuilder() = default; + +auto UEcsactRunner::EcsactRunnerCreateEntityBuilder::Finish() -> void { + Builder.Finish(); +} + +auto UEcsactRunner::EcsactRunnerCreateEntityBuilder::OnCreate( + TDelegate Callback +) && -> EcsactRunnerCreateEntityBuilder { + Owner->CreateEntityCallbacks.Add(PlaceholderId, Callback); + return std::move(*this); } diff --git a/Source/Ecsact/Public/EcsactUnreal/EcsactRunner.h b/Source/Ecsact/Public/EcsactUnreal/EcsactRunner.h index 5f7e15d..be4a6c3 100644 --- a/Source/Ecsact/Public/EcsactUnreal/EcsactRunner.h +++ b/Source/Ecsact/Public/EcsactUnreal/EcsactRunner.h @@ -8,21 +8,85 @@ UCLASS(Abstract) -class UEcsactRunner : public UObject, public FTickableGameObject { +class ECSACT_API UEcsactRunner : public UObject, public FTickableGameObject { GENERATED_BODY() // NOLINT -protected: - UPROPERTY() - class UEcsactUnrealEventsCollector* EventsCollector; + TArray RunnerSubsystems; + ecsact_execution_events_collector EventsCollector; + bool bIsStopped = false; + + TMap> + CreateEntityCallbacks; + + static auto OnInitComponentRaw( + ecsact_event event, + ecsact_entity_id entity_id, + ecsact_component_id component_id, + const void* component_data, + void* callback_user_data + ) -> void; + + static auto OnUpdateComponentRaw( + ecsact_event event, + ecsact_entity_id entity_id, + ecsact_component_id component_id, + const void* component_data, + void* callback_user_data + ) -> void; + + static auto OnRemoveComponentRaw( + ecsact_event event, + ecsact_entity_id entity_id, + ecsact_component_id component_id, + const void* component_data, + void* callback_user_data + ) -> void; + + static auto OnEntityCreatedRaw( + ecsact_event event, + ecsact_entity_id entity_id, + ecsact_placeholder_entity_id placeholder_entity_id, + void* callback_user_data + ) -> void; + static auto OnEntityDestroyedRaw( + ecsact_event event, + ecsact_entity_id entity_id, + ecsact_placeholder_entity_id placeholder_entity_id, + void* callback_user_data + ) -> void; + +protected: UPROPERTY() class UEcsactUnrealExecutionOptions* ExecutionOptions; + auto GetEventsCollector() -> ecsact_execution_events_collector*; + + virtual auto InitRunnerSubsystems() -> void; + virtual auto ShutdownRunnerSubsystems() -> void; + + virtual auto GeneratePlaceholderId() -> ecsact_placeholder_entity_id; + public: + class EcsactRunnerCreateEntityBuilder; + + UEcsactRunner(); + + virtual auto Start() -> void; + virtual auto Stop() -> void; + virtual auto IsStopped() const -> bool; + auto Tick(float DeltaTime) -> void override; auto GetStatId() const -> TStatId override; auto IsTickable() const -> bool override; + /** + * Returns a builder for creating a new entity. This builder is 'runner aware' + * meaning that any lifecycle hooks that the builder exposes is provided by + * the runners execution + */ + auto CreateEntity() -> EcsactRunnerCreateEntityBuilder; + template auto PushAction(const A& Action) -> void { return ExecutionOptions->PushAction(Action); @@ -43,3 +107,43 @@ class UEcsactRunner : public UObject, public FTickableGameObject { return ExecutionOptions->RemoveComponent(Entity); } }; + +class ECSACT_API UEcsactRunner::EcsactRunnerCreateEntityBuilder { + friend UEcsactRunner; + + UEcsactRunner* Owner; + ecsact_placeholder_entity_id PlaceholderId; + + UEcsactUnrealExecutionOptions::CreateEntityBuilder Builder; + + EcsactRunnerCreateEntityBuilder( + UEcsactRunner* Owner, + ecsact_placeholder_entity_id PlacerholderId + ); + +public: + EcsactRunnerCreateEntityBuilder(EcsactRunnerCreateEntityBuilder&&); + ~EcsactRunnerCreateEntityBuilder(); + + template + auto AddComponent( // + const C& Component + ) && -> EcsactRunnerCreateEntityBuilder { + static_cast(Builder) + .AddComponent(Component); + return std::move(*this); + } + + /** + * Listens for when the entity is created. + */ + auto OnCreate( // + TDelegate Callback + ) && -> EcsactRunnerCreateEntityBuilder; + + /** + * This is automatically called by the destructor, but can be called manually + * to 'finish' building your entity. + */ + auto Finish() -> void; +}; diff --git a/Source/Ecsact/Public/EcsactUnreal/EcsactRunnerSubsystem.cpp b/Source/Ecsact/Public/EcsactUnreal/EcsactRunnerSubsystem.cpp new file mode 100644 index 0000000..523751f --- /dev/null +++ b/Source/Ecsact/Public/EcsactUnreal/EcsactRunnerSubsystem.cpp @@ -0,0 +1,42 @@ +#include "EcsactRunnerSubsystem.h" + +auto UEcsactRunnerSubsystem::InitComponentRaw( + ecsact_entity_id EntityId, + ecsact_component_id ComponentId, + const void* ComponentData +) -> void { +} + +auto UEcsactRunnerSubsystem::UpdateComponentRaw( + ecsact_entity_id EntityId, + ecsact_component_id ComponentId, + const void* ComponentData +) -> void { +} + +auto UEcsactRunnerSubsystem::RemoveComponentRaw( + ecsact_entity_id EntityId, + ecsact_component_id ComponentId, + const void* ComponentData +) -> void { +} + +auto UEcsactRunnerSubsystem::RunnerStart_Implementation( + class UEcsactRunner* Runner +) -> void { +} + +auto UEcsactRunnerSubsystem::RunnerStop_Implementation( + class UEcsactRunner* Runner +) -> void { +} + +auto UEcsactRunnerSubsystem::EntityCreated_Implementation( // + int32 Entity +) -> void { +} + +auto UEcsactRunnerSubsystem::EntityDestroyed_Implementation( // + int32 Entity +) -> void { +} diff --git a/Source/Ecsact/Public/EcsactUnreal/EcsactRunnerSubsystem.h b/Source/Ecsact/Public/EcsactUnreal/EcsactRunnerSubsystem.h new file mode 100644 index 0000000..04e2e43 --- /dev/null +++ b/Source/Ecsact/Public/EcsactUnreal/EcsactRunnerSubsystem.h @@ -0,0 +1,57 @@ +#pragma once + +#include "ecsact/runtime/common.h" +#include "EcsactRunnerSubsystem.generated.h" + +UCLASS(Abstract, Blueprintable) + +class ECSACT_API UEcsactRunnerSubsystem : public UObject { + GENERATED_BODY() // NOLINT + + friend class UEcsactRunner; + +protected: + virtual void InitComponentRaw( + ecsact_entity_id EntityId, + ecsact_component_id ComponentId, + const void* ComponentData + ); + virtual void UpdateComponentRaw( + ecsact_entity_id EntityId, + ecsact_component_id ComponentId, + const void* ComponentData + ); + virtual void RemoveComponentRaw( + ecsact_entity_id EntityId, + ecsact_component_id ComponentId, + const void* ComponentData + ); + +public: + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner") + void RunnerStart(class UEcsactRunner* Runner); + + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner") + void RunnerStop(class UEcsactRunner* Runner); + + virtual auto RunnerStart_Implementation( // + class UEcsactRunner* Runner + ) -> void; + virtual auto RunnerStop_Implementation( // + class UEcsactRunner* Runner + ) -> void; + + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner") + void EntityCreated(int32 Entity); + + UFUNCTION(BlueprintNativeEvent, Category = "Ecsact Runner") + void EntityDestroyed(int32 Entity); + + virtual auto EntityCreated_Implementation( // + int32 Entity + ) -> void; + + virtual auto EntityDestroyed_Implementation( // + int32 Entity + ) -> void; +}; diff --git a/Source/Ecsact/Public/EcsactUnreal/EcsactSettings.h b/Source/Ecsact/Public/EcsactUnreal/EcsactSettings.h index 5d111c8..a24b5b7 100644 --- a/Source/Ecsact/Public/EcsactUnreal/EcsactSettings.h +++ b/Source/Ecsact/Public/EcsactUnreal/EcsactSettings.h @@ -55,4 +55,7 @@ class ECSACT_API UEcsactSettings : public UObject { ) ) TSubclassOf CustomRunnerClass; + + UPROPERTY(VisibleAnywhere, Config, Category = "Runtime") + TArray> RunnerSubsystems; }; diff --git a/Source/Ecsact/Public/EcsactUnreal/EcsactSyncRunner.cpp b/Source/Ecsact/Public/EcsactUnreal/EcsactSyncRunner.cpp index 1ee2f1a..24ed079 100644 --- a/Source/Ecsact/Public/EcsactUnreal/EcsactSyncRunner.cpp +++ b/Source/Ecsact/Public/EcsactUnreal/EcsactSyncRunner.cpp @@ -23,17 +23,13 @@ auto UEcsactSyncRunner::Tick(float DeltaTime) -> void { registry_id = ecsact_create_registry("Default Registry"); } - ecsact_execution_events_collector* evc_c = nullptr; - if(EventsCollector != nullptr) { - evc_c = EventsCollector->GetCEVC(); - } - ecsact_execution_options* exec_opts = nullptr; if(ExecutionOptions != nullptr && ExecutionOptions->IsNotEmpty()) { exec_opts = ExecutionOptions->GetCPtr(); } - auto err = ecsact_execute_systems(registry_id, 1, exec_opts, evc_c); + auto err = + ecsact_execute_systems(registry_id, 1, exec_opts, GetEventsCollector()); if(err != ECSACT_EXEC_SYS_OK) { UE_LOG(Ecsact, Error, TEXT("Ecsact execution failed")); } diff --git a/Source/Ecsact/Public/EcsactUnreal/EcsactUnrealExecutionOptions.cpp b/Source/Ecsact/Public/EcsactUnreal/EcsactUnrealExecutionOptions.cpp index ec0f144..b73b87f 100644 --- a/Source/Ecsact/Public/EcsactUnreal/EcsactUnrealExecutionOptions.cpp +++ b/Source/Ecsact/Public/EcsactUnreal/EcsactUnrealExecutionOptions.cpp @@ -1,4 +1,7 @@ #include "EcsactUnreal/EcsactUnrealExecutionOptions.h" +#include "ecsact/runtime/common.h" + +using CreateEntityBuilder = UEcsactUnrealExecutionOptions::CreateEntityBuilder; UEcsactUnrealExecutionOptions::UEcsactUnrealExecutionOptions() : ExecOpts({}) { } @@ -12,7 +15,8 @@ auto UEcsactUnrealExecutionOptions::IsNotEmpty() const -> bool { ExecOpts.add_components_length > 0 || ExecOpts.destroy_entities_length > 0 || ExecOpts.update_components_length > 0 || - ExecOpts.remove_components_length > 0; + ExecOpts.remove_components_length > 0 || + ExecOpts.create_entities_length > 0; } auto UEcsactUnrealExecutionOptions::Clear() -> void { @@ -29,5 +33,70 @@ auto UEcsactUnrealExecutionOptions::Clear() -> void { AddComponentList.Empty(); UpdateComponentList.Empty(); RemoveComponentList.Empty(); + DestroyEntityList.Empty(); + CreateEntityList.Empty(); + CreateEntityComponentsList.Empty(); + CreateEntityComponentsListData.Empty(); + CreateEntityComponentsListNums.Empty(); ExecOpts = {}; } + +auto UEcsactUnrealExecutionOptions::CreateEntity( + ecsact_placeholder_entity_id PlaceholderId +) -> CreateEntityBuilder { + return {this, PlaceholderId}; +} + +CreateEntityBuilder::CreateEntityBuilder( + UEcsactUnrealExecutionOptions* Owner, + ecsact_placeholder_entity_id PlacerholderId +) + : bValid(true), Owner(Owner), PlaceholderId(PlaceholderId) { +} + +CreateEntityBuilder::CreateEntityBuilder(CreateEntityBuilder&& Other) { + bValid = Other.bValid; + Owner = Other.Owner; + ComponentList = std::move(Other.ComponentList); + + Other.bValid = false; + Other.Owner = nullptr; + Other.ComponentList = {}; +} + +CreateEntityBuilder::~CreateEntityBuilder() { + if(bValid && Owner) { + Finish(); + } +} + +auto CreateEntityBuilder::Finish() -> void { + if(!bValid || !Owner) { + return; + } + + UE_LOG( + LogTemp, + Warning, + TEXT("CreateEntityBuilder::Finish() after valid check") + ); + + Owner->CreateEntityList.Add(PlaceholderId); + Owner->CreateEntityComponentsListNums.Add(ComponentList.Num()); + Owner->CreateEntityComponentsList.Push(std::move(ComponentList)); + Owner->CreateEntityComponentsListData.Empty(); + for(auto& list : Owner->CreateEntityComponentsList) { + Owner->CreateEntityComponentsListData.Add(list.GetData()); + } + + Owner->ExecOpts.create_entities_length = Owner->CreateEntityList.Num(); + Owner->ExecOpts.create_entities = Owner->CreateEntityList.GetData(); + Owner->ExecOpts.create_entities_components_length = + Owner->CreateEntityComponentsListNums.GetData(); + Owner->ExecOpts.create_entities_components = + Owner->CreateEntityComponentsListData.GetData(); + + bValid = false; + Owner = nullptr; + ComponentList = {}; +} diff --git a/Source/Ecsact/Public/EcsactUnreal/EcsactUnrealExecutionOptions.h b/Source/Ecsact/Public/EcsactUnreal/EcsactUnrealExecutionOptions.h index 7ea0d6e..ab2a306 100644 --- a/Source/Ecsact/Public/EcsactUnreal/EcsactUnrealExecutionOptions.h +++ b/Source/Ecsact/Public/EcsactUnreal/EcsactUnrealExecutionOptions.h @@ -7,16 +7,28 @@ UCLASS() -class UEcsactUnrealExecutionOptions : public UObject { +class ECSACT_API UEcsactUnrealExecutionOptions : public UObject { GENERATED_BODY() // NOLINT TArray ActionList; TArray AddComponentList; TArray UpdateComponentList; TArray RemoveComponentList; - ecsact_execution_options ExecOpts; + + TArray CreateEntityList; + TArray> CreateEntityComponentsList; + + TArray CreateEntityComponentsListData; + TArray CreateEntityComponentsListNums; + + TArray DestroyEntityList; + + ecsact_execution_options ExecOpts; public: + class CreateEntityBuilder; + friend CreateEntityBuilder; + UEcsactUnrealExecutionOptions(); auto Clear() -> void; @@ -30,6 +42,18 @@ class UEcsactUnrealExecutionOptions : public UObject { */ auto GetCPtr() -> ecsact_execution_options*; + /** + * Create an entity. You may give a placeholder ID that will later be used in + * the events collector create entity event. + */ + auto CreateEntity( // + ecsact_placeholder_entity_id PlaceholderEntityId + ) -> CreateEntityBuilder; + + inline auto DestroyEntity(ecsact_entity_id Entity) -> void { + DestroyEntityList.Add(Entity); + } + template auto PushAction(const A& Action) -> void { auto action_data = FMemory::Malloc(sizeof(A)); @@ -77,3 +101,37 @@ class UEcsactUnrealExecutionOptions : public UObject { ExecOpts.remove_components = RemoveComponentList.GetData(); } }; + +class ECSACT_API UEcsactUnrealExecutionOptions::CreateEntityBuilder { + friend class UEcsactUnrealExecutionOptions; + bool bValid; + UEcsactUnrealExecutionOptions* Owner; + ecsact_placeholder_entity_id PlaceholderId; + TArray ComponentList; + + CreateEntityBuilder( + UEcsactUnrealExecutionOptions* Owner, + ecsact_placeholder_entity_id PlacerholderId + ); + +public: + CreateEntityBuilder(CreateEntityBuilder&&); + ~CreateEntityBuilder(); + + template + auto AddComponent(const C& Component) && -> CreateEntityBuilder { + auto component_data = FMemory::Malloc(sizeof(C)); + FMemory::Memcpy(component_data, &Component, sizeof(C)); + ComponentList.Push(ecsact_component{ + .component_id = C::id, + .component_data = component_data, + }); + return std::move(*this); + } + + /** + * This is automatically called by the destructor, but can be called manually + * to 'finish' building your entity. + */ + auto Finish() -> void; +}; diff --git a/Source/EcsactUnrealCodegenPlugin/EcsactUnrealCodegenPlugin.cpp b/Source/EcsactUnrealCodegenPlugin/EcsactUnrealCodegenPlugin.cpp index 3f8d9b2..f197480 100644 --- a/Source/EcsactUnrealCodegenPlugin/EcsactUnrealCodegenPlugin.cpp +++ b/Source/EcsactUnrealCodegenPlugin/EcsactUnrealCodegenPlugin.cpp @@ -52,7 +52,7 @@ auto ecsact_decl_name_to_pascal(const std::string& input) -> std::string { auto capitalize_next = true; for(char ch : input) { - if(ch == '.') { + if(ch == '.' || ch == '_') { capitalize_next = true; // Capitalize the next character after a separator } else if(capitalize_next) { result << static_cast(std::toupper(ch)); @@ -93,11 +93,159 @@ auto ecsact_codegen_plugin_name() -> const char* { return "Unreal"; } +static auto ecsact_type_to_unreal_type( + ecsact::codegen_plugin_context& ctx, + ecsact_builtin_type type +) -> std::string { + switch(type) { + case ECSACT_BOOL: + return "bool"; + case ECSACT_I8: + case ECSACT_I16: + case ECSACT_I32: + return "int32"; + case ECSACT_U8: + case ECSACT_U16: + case ECSACT_U32: + return "uint32"; + case ECSACT_F32: + return "float"; + case ECSACT_ENTITY_TYPE: + break; + default: + ctx.fatal( + "Ecsact Unreal codegen plugin unknown builtin type ({}). Cannot " + "proceed. Are you using the correct version of the Ecsact SDK with the " + "correct version of the Ecsact Unreal codegen plugin?", + static_cast(type) + ); + break; + } + + return ""; +} + +static auto ecsact_type_to_unreal_type( + ecsact::codegen_plugin_context& ctx, + ecsact_field_type type +) -> std::string { + switch(type.kind) { + case ECSACT_TYPE_KIND_BUILTIN: + if(type.length == 1) { + return ecsact_type_to_unreal_type(ctx, type.type.builtin); + } + ctx.fatal("Ecsact builtin array fields not supported yet"); + break; + case ECSACT_TYPE_KIND_ENUM: + ctx.fatal("Esact enum type not supported in yet"); + break; + case ECSACT_TYPE_KIND_FIELD_INDEX: + ctx.fatal("Ecsact field index not supported in unreal yet"); + break; + default: + ctx.fatal( + "Ecsact Unreal codegen plugin unknown field type (kind={}). Cannot " + "proceed. Are you using the correct version of the Ecsact SDK with the " + "correct version of the Ecsact Unreal codegen plugin?", + static_cast(type.kind) + ); + break; + } + + return ""; +} + +static auto ecsact_ustruct_name(auto decl_id) -> std::string { + auto name = ecsact::meta::decl_full_name(decl_id); + auto pascal_name = ecsact_decl_name_to_pascal(name); + return std::format("F{}", pascal_name); +} + +template +static auto uproperty_clamp_min_max() -> std::string { + return std::format( + R"(, Meta = (ClampMin = "{}", ClampMax = "{}"))", + std::numeric_limits::min(), + std::numeric_limits::max() + ); +} + +static auto print_ecsact_type_uproperty( + ecsact::codegen_plugin_context& ctx, + ecsact_field_type field_type +) -> void { + ctx.write("UPROPERTY(EditAnywhere, BlueprintReadWrite"); + switch(field_type.kind) { + case ECSACT_TYPE_KIND_BUILTIN: + switch(field_type.type.builtin) { + case ECSACT_BOOL: + break; + case ECSACT_I8: + ctx.write(uproperty_clamp_min_max()); + break; + case ECSACT_U8: + ctx.write(uproperty_clamp_min_max()); + break; + case ECSACT_I16: + ctx.write(uproperty_clamp_min_max()); + break; + case ECSACT_U16: + ctx.write(uproperty_clamp_min_max()); + break; + case ECSACT_I32: + break; + case ECSACT_U32: + break; + case ECSACT_F32: + break; + case ECSACT_ENTITY_TYPE: + break; + } + break; + case ECSACT_TYPE_KIND_ENUM: + break; + case ECSACT_TYPE_KIND_FIELD_INDEX: + break; + } + + ctx.write(")\n"); +} + +static auto print_ustruct(ecsact::codegen_plugin_context& ctx, auto in_compo_id) + -> void { + auto compo_id = ecsact_id_cast(in_compo_id); + auto compo_name = ecsact::meta::decl_full_name(compo_id); + auto compo_pascal_name = ecsact_ustruct_name(compo_id); + + ctx.writef("USTRUCT(BlueprintType)\n"); + block(ctx, std::format("struct {}", compo_pascal_name), [&] { + ctx.writef("GENERATED_BODY()\n\n"); + auto fields = ecsact::meta::get_field_ids(compo_id); + + ctx.write(std::format( + "static {} FromEcsactComponentData(const void*);\n", + compo_pascal_name + )); + + for(auto field_id : fields) { + auto field_type = ecsact::meta::get_field_type(compo_id, field_id); + auto field_unreal_type = ecsact_type_to_unreal_type(ctx, field_type); + auto field_name = ecsact::meta::field_name(compo_id, field_id); + auto field_pascal_name = ecsact_decl_name_to_pascal(field_name); + + print_ecsact_type_uproperty(ctx, field_type); + ctx.write(std::format("{} {};\n", field_unreal_type, field_pascal_name)); + } + }); + ctx.writef(";\n\n"); +} + static auto generate_header(ecsact::codegen_plugin_context ctx) -> void { ctx.writef("#pragma once\n\n"); inc_header(ctx, "CoreMinimal.h"); inc_header(ctx, "UObject/Interface.h"); + inc_header(ctx, "EcsactUnreal/EcsactRunnerSubsystem.h"); inc_package_header(ctx, ctx.package_id, ".hh"); inc_package_header_no_ext(ctx, ctx.package_id, "__ecsact__ue.generated.h"); @@ -107,43 +255,392 @@ static auto generate_header(ecsact::codegen_plugin_context ctx) -> void { ctx.writef("\n\n"); for(auto comp_id : ecsact::meta::get_component_ids(ctx.package_id)) { + print_ustruct(ctx, comp_id); + } + + ctx.write(std::format( + "UCLASS(Abstract, Blueprintable, meta = " + "(DisplayName = \"Ecsact Runner Package Subsystem ({})\"))\n", + ecsact::meta::package_name(ctx.package_id) + )); + block( + ctx, + std::format( + "class U{}EcsactRunnerSubsystem : public UEcsactRunnerSubsystem", + prefix + ), + [&] { + ctx.writef("GENERATED_BODY() // NOLINT\n\n"); + + ctx.write( + "TArray InitComponentFns;\n" + "TArray UpdateComponentFns;\n" + "TArray RemoveComponentFns;\n" + ); + + for(auto comp_id : ecsact::meta::get_component_ids(ctx.package_id)) { + auto comp_full_name = ecsact::meta::decl_full_name(comp_id); + auto comp_type_cpp_name = cpp_identifier(comp_full_name); + auto comp_name = ecsact::meta::component_name(comp_id); + auto comp_pascal_name = ecsact_decl_name_to_pascal(comp_name); + + ctx.write(std::format( + "void RawInit{0}(int32 Entity, const void* Component);\n", + comp_pascal_name + )); + + ctx.write(std::format( + "void RawUpdate{0}(int32 Entity, const void* Component);\n", + comp_pascal_name + )); + + ctx.write(std::format( + "void RawRemove{0}(int32 Entity, const void* Component);\n", + comp_pascal_name + )); + } + + ctx.indentation -= 1; + ctx.writef("\n"); + ctx.writef("protected:"); + ctx.indentation += 1; + ctx.writef("\n"); + + ctx.write( + "void InitComponentRaw(" + "ecsact_entity_id, ecsact_component_id, const void*) override;\n" + "void UpdateComponentRaw(" + "ecsact_entity_id, ecsact_component_id, const void*) override;\n" + "void RemoveComponentRaw(" + "ecsact_entity_id, ecsact_component_id, const void*) override;\n\n" + ); + + ctx.indentation -= 1; + ctx.writef("\n"); + ctx.writef("public:"); + ctx.indentation += 1; + ctx.writef("\n"); + + ctx.write(std::format("U{}EcsactRunnerSubsystem();\n", prefix)); + + for(auto comp_id : ecsact::meta::get_component_ids(ctx.package_id)) { + auto comp_full_name = ecsact::meta::decl_full_name(comp_id); + auto comp_type_cpp_name = cpp_identifier(comp_full_name); + auto comp_name = ecsact::meta::component_name(comp_id); + auto comp_pascal_name = ecsact_decl_name_to_pascal(comp_name); + auto comp_ustruct_name = ecsact_ustruct_name(comp_id); + ctx.write(std::format( + "UFUNCTION(BlueprintNativeEvent, Category = \"Ecsact Runner\", " + "meta = (DisplayName = \"Init {}\"))\n", + comp_full_name + )); + ctx.write(std::format( + "void Init{0}(int32 Entity, {1} {0});\n", + comp_pascal_name, + comp_ustruct_name + )); + ctx.write(std::format( + "virtual void Init{0}_Implementation(int32 Entity, {1} {0});\n", + comp_pascal_name, + comp_ustruct_name + )); + + ctx.write(std::format( + "UFUNCTION(BlueprintNativeEvent, Category = \"Ecsact Runner\", " + "meta = (DisplayName = \"Update {}\"))\n", + comp_full_name + )); + ctx.write(std::format( + "void Update{0}(int32 Entity, {1} {0});\n", + comp_pascal_name, + comp_ustruct_name + )); + ctx.write(std::format( + "virtual void Update{0}_Implementation(int32 Entity, {1} {0});\n", + comp_pascal_name, + comp_ustruct_name + )); + + ctx.write(std::format( + "UFUNCTION(BlueprintNativeEvent, Category = \"Ecsact Runner\", " + "meta = (DisplayName = \"Remove {}\"))\n", + comp_full_name + )); + ctx.write(std::format( + "void Remove{0}(int32 Entity, {1} {0});\n", + comp_pascal_name, + comp_ustruct_name + )); + ctx.write(std::format( + "virtual void Remove{0}_Implementation(int32 Entity, {1} {0});\n", + comp_pascal_name, + comp_ustruct_name + )); + } + } + ); + ctx.writef(";\n"); +} + +static auto generate_source(ecsact::codegen_plugin_context ctx) -> void { + inc_package_header_no_ext(ctx, ctx.package_id, "__ecsact__ue.h"); + + auto package_pascal_name = + ecsact_decl_name_to_pascal(ecsact::meta::package_name(ctx.package_id)); + + auto comp_ids = ecsact::meta::get_component_ids(ctx.package_id); + auto largest_comp_id = 0; + for(auto comp_id : comp_ids) { + if(static_cast(comp_id) > largest_comp_id) { + largest_comp_id = static_cast(comp_id); + } + } + + for(auto comp_id : comp_ids) { + auto comp_full_name = ecsact::meta::decl_full_name(comp_id); + auto comp_name = ecsact::meta::component_name(comp_id); + auto comp_type_cpp_name = cpp_identifier(comp_full_name); + auto comp_pascal_name = ecsact_decl_name_to_pascal(comp_name); + auto comp_ustruct_name = ecsact_ustruct_name(comp_id); + block( + ctx, + std::format( + "{0} {0}::FromEcsactComponentData(const void* component_data)", + comp_ustruct_name + ), + [&] { + ctx.write(std::format("auto result = {0}{{}};\n", comp_ustruct_name)); + + for(auto field_id : ecsact::meta::get_field_ids(comp_id)) { + auto field_type = ecsact::meta::get_field_type(comp_id, field_id); + auto field_unreal_type = ecsact_type_to_unreal_type(ctx, field_type); + auto field_name = ecsact::meta::field_name(comp_id, field_id); + auto field_pascal_name = ecsact_decl_name_to_pascal(field_name); + + ctx.write(std::format( // + "result.{0} = static_cast(component_data)->{2};\n", + field_pascal_name, + comp_type_cpp_name, + field_name + )); + } + + ctx.write("return result;"); + } + ); + ctx.write("\n"); + } + + block( + ctx, + std::format( + "U{0}EcsactRunnerSubsystem::U{0}EcsactRunnerSubsystem()", + package_pascal_name + ), + [&] { + ctx.write("InitComponentFns.Init(nullptr, ", largest_comp_id + 1, ");\n"); + ctx.write( + "UpdateComponentFns.Init(nullptr, ", + largest_comp_id + 1, + ");\n" + ); + ctx.write( + "RemoveComponentFns.Init(nullptr, ", + largest_comp_id + 1, + ");\n" + ); + + for(auto comp_id : comp_ids) { + auto comp_full_name = ecsact::meta::decl_full_name(comp_id); + auto comp_name = ecsact::meta::component_name(comp_id); + auto comp_pascal_name = ecsact_decl_name_to_pascal(comp_name); + ctx.write(std::format( + "InitComponentFns[{}] = &ThisClass::RawInit{};\n", + static_cast(comp_id), + comp_pascal_name + )); + ctx.write(std::format( + "UpdateComponentFns[{}] = &ThisClass::RawUpdate{};\n", + static_cast(comp_id), + comp_pascal_name + )); + ctx.write(std::format( + "RemoveComponentFns[{}] = &ThisClass::RawRemove{};\n", + static_cast(comp_id), + comp_pascal_name + )); + } + } + ); + ctx.write("\n\n"); + + block( + ctx, + std::format( + "void U{0}EcsactRunnerSubsystem::InitComponentRaw" + "( ecsact_entity_id entity" + ", ecsact_component_id component_id" + ", const void* component_data)", + package_pascal_name + ), + [&] { + ctx.write( + "(this->*InitComponentFns[static_cast(component_id)])" + "(static_cast(entity), component_data);" + ); + } + ); + ctx.writef("\n\n"); + + block( + ctx, + std::format( + "void U{0}EcsactRunnerSubsystem::UpdateComponentRaw" + "( ecsact_entity_id entity" + ", ecsact_component_id component_id" + ", const void* component_data)", + package_pascal_name + ), + [&] { + ctx.write( + "(this->*UpdateComponentFns[static_cast(component_id)])" + "(static_cast(entity), component_data);" + ); + } + ); + ctx.writef("\n\n"); + + block( + ctx, + std::format( + "void U{0}EcsactRunnerSubsystem::RemoveComponentRaw" + "( ecsact_entity_id entity" + ", ecsact_component_id component_id" + ", const void* component_data)", + package_pascal_name + ), + [&] { + ctx.write( + "(this->*RemoveComponentFns[static_cast(component_id)])" + "(static_cast(entity), component_data);" + ); + } + ); + ctx.writef("\n\n"); + + for(auto comp_id : comp_ids) { + auto comp_full_name = ecsact::meta::decl_full_name(comp_id); auto comp_name = ecsact::meta::component_name(comp_id); - ctx.write( - "UINTERFACE(MinimalAPI, Blueprintable, meta = (DisplayName = \"", + auto comp_pascal_name = ecsact_decl_name_to_pascal(comp_name); + auto comp_ustruct_name = ecsact_ustruct_name(comp_id); + auto comp_type_cpp_name = cpp_identifier(comp_full_name); + + block( + ctx, std::format( - "requires {} (ecsact)", - ecsact::meta::decl_full_name(comp_id) + "void U{0}EcsactRunnerSubsystem::RawInit{1}" + "(int32 entity, const void* component)", + package_pascal_name, + comp_pascal_name ), - "\"))\n" + [&] { + ctx.write(std::format( + "Init{0}(entity, {1}::FromEcsactComponentData(component));", + comp_pascal_name, + comp_ustruct_name + )); + } ); + ctx.write("\n"); + block( ctx, std::format( - "class UEcsactRequires{}{} : public UInterface", - prefix, - comp_name + "void U{0}EcsactRunnerSubsystem::RawUpdate{1}" + "(int32 entity, const void* component)", + package_pascal_name, + comp_pascal_name ), - [&] { ctx.writef("GENERATED_BODY() // NOLINT"); } + [&] { + ctx.write(std::format( + "Update{0}(entity, {1}::FromEcsactComponentData(component));", + comp_pascal_name, + comp_ustruct_name + )); + } ); - ctx.writef(";\n"); + ctx.write("\n"); block( ctx, - std::format("class IEcsactRequires{}{}", prefix, comp_name), + std::format( + "void U{0}EcsactRunnerSubsystem::RawRemove{1}" + "(int32 entity, const void* component)", + package_pascal_name, + comp_pascal_name + ), [&] { - ctx.writef("GENERATED_BODY() // NOLINT\n"); - ctx.indentation -= 1; - ctx.writef("\n"); - ctx.writef("public:"); - ctx.indentation += 1; - ctx.writef("\n"); + ctx.write(std::format( + "Remove{0}(entity, {1}::FromEcsactComponentData(component));", + comp_pascal_name, + comp_ustruct_name + )); } ); - ctx.writef(";\n"); + ctx.write("\n"); } -} -static auto generate_source(ecsact::codegen_plugin_context ctx) -> void { + for(auto comp_id : comp_ids) { + auto comp_full_name = ecsact::meta::decl_full_name(comp_id); + auto comp_name = ecsact::meta::component_name(comp_id); + auto comp_pascal_name = ecsact_decl_name_to_pascal(comp_name); + auto comp_ustruct_name = ecsact_ustruct_name(comp_id); + + block( + ctx, + std::format( + "void U{0}EcsactRunnerSubsystem::Init{1}_Implementation" + "(int32 Entity, {2} {1})", + package_pascal_name, + comp_pascal_name, + comp_ustruct_name + ), + [&] { + + } + ); + ctx.writef("\n\n"); + + block( + ctx, + std::format( + "void U{0}EcsactRunnerSubsystem::Update{1}_Implementation" + "(int32 Entity, {2} {1})", + package_pascal_name, + comp_pascal_name, + comp_ustruct_name + ), + [&] { + + } + ); + ctx.writef("\n\n"); + + block( + ctx, + std::format( + "void U{0}EcsactRunnerSubsystem::Remove{1}_Implementation" + "(int32 Entity, {2} {1})", + package_pascal_name, + comp_pascal_name, + comp_ustruct_name + ), + [&] { + + } + ); + ctx.writef("\n\n"); + } } auto ecsact_codegen_plugin(