Add twisty Bound 3 adapter bootstrap packet
This commit is contained in:
parent
bcd1f9ff4e
commit
dc621d97e4
4 changed files with 634 additions and 2 deletions
|
|
@ -8,6 +8,9 @@
|
|||
|
||||
#include "HyperTwistReplay/HyperTwistReplayLibrary.h"
|
||||
#include "HyperTwistSimulation/HyperTwistViewerLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingClassicCubingLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingSpatialLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingViewerLibrary.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
|
||||
namespace HyperTwistPuzzleViewerComponentInternal
|
||||
|
|
@ -288,6 +291,23 @@ namespace HyperTwistPuzzleViewerComponentInternal
|
|||
return PreviousBoundaryTimeMs;
|
||||
}
|
||||
|
||||
TArray<int32> ResolveProjectionBoundaryTimesMs(
|
||||
const TArray<FHyperTwistViewerMoveEntry>& MoveList,
|
||||
const TArray<int32>& CachedMoveBoundaryTimesMs
|
||||
)
|
||||
{
|
||||
return CachedMoveBoundaryTimesMs.Num() == MoveList.Num()
|
||||
? CachedMoveBoundaryTimesMs
|
||||
: BuildMoveBoundaryTimesMs(MoveList);
|
||||
}
|
||||
|
||||
int32 ResolveProjectionMoveStartTimeMs(const TArray<int32>& MoveBoundaryTimesMs, const int32 MoveIndex)
|
||||
{
|
||||
return MoveIndex > 0 && MoveBoundaryTimesMs.IsValidIndex(MoveIndex - 1)
|
||||
? MoveBoundaryTimesMs[MoveIndex - 1]
|
||||
: 0;
|
||||
}
|
||||
|
||||
FHyperTwistViewerPositionSnapshot BuildPositionSnapshot(
|
||||
const TArray<FHyperTwistViewerMoveEntry>& MoveList,
|
||||
const TArray<int32>& MoveBoundaryTimesMs,
|
||||
|
|
@ -466,6 +486,7 @@ void UHyperTwistPuzzleViewerComponent::LoadSceneContext(const FHyperTwistSimulat
|
|||
|
||||
SceneContext = InContext;
|
||||
PlaybackState.bSceneContextLoaded = InContext.IsStructurallyValid();
|
||||
HostBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
|
||||
// Reset playhead — loaded content has changed.
|
||||
PlaybackState.Mode = EHyperTwistViewerPlaybackMode::Stopped;
|
||||
|
|
@ -485,6 +506,7 @@ void UHyperTwistPuzzleViewerComponent::LoadMoveList(const TArray<FHyperTwistView
|
|||
ReplayPacket = FHyperTwistReplayPacket();
|
||||
ReplaySummary = FHyperTwistDerivedReplaySummary();
|
||||
bReplayPacketLoaded = false;
|
||||
HostBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
|
||||
PlaybackState.Mode = EHyperTwistViewerPlaybackMode::Stopped;
|
||||
PlaybackState.Direction = EHyperTwistViewerPlaybackDirection::None;
|
||||
|
|
@ -508,6 +530,7 @@ bool UHyperTwistPuzzleViewerComponent::LoadReplayPacket(const FHyperTwistReplayP
|
|||
ReplaySummary = UHyperTwistReplayLibrary::DeriveReplaySummary(InReplayPacket);
|
||||
bReplayPacketLoaded = true;
|
||||
MoveList = HyperTwistPuzzleViewerComponentInternal::BuildReplayMoveList(InReplayPacket);
|
||||
HostBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
|
||||
FHyperTwistSimulationSceneContext ReplaySceneContext;
|
||||
if (HyperTwistPuzzleViewerComponentInternal::TryBuildSceneContextFromReplay(InReplayPacket, ReplaySceneContext))
|
||||
|
|
@ -706,6 +729,153 @@ bool UHyperTwistPuzzleViewerComponent::AdvancePlaybackByMs(const int32 DeltaTime
|
|||
return PlaybackState.CurrentTimeMs != PreviousTimeMs;
|
||||
}
|
||||
|
||||
bool UHyperTwistPuzzleViewerComponent::TryBuildClassicCubingAdapterProjection(
|
||||
FHyperTwistViewerClassicCubingAdapterProjection& OutProjection
|
||||
) const
|
||||
{
|
||||
OutProjection = FHyperTwistViewerClassicCubingAdapterProjection();
|
||||
|
||||
if (!SceneContext.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingClassicCubingReferenceBundle Bundle =
|
||||
UHyperTwistTrainingClassicCubingLibrary::BuildBundledClassicCubingReferenceBundle();
|
||||
|
||||
FHyperTwistTrainingClassicCubingSemanticContract SemanticContract;
|
||||
FHyperTwistTrainingClassicCubingGeometryContract GeometryContract;
|
||||
FHyperTwistTrainingClassicCubingViewerAdapterContract ViewerAdapterContract;
|
||||
|
||||
if (!UHyperTwistTrainingClassicCubingLibrary::TryGetSemanticContractById(
|
||||
Bundle,
|
||||
Bundle.PrimarySemanticContractId,
|
||||
SemanticContract
|
||||
)
|
||||
|| !UHyperTwistTrainingClassicCubingLibrary::TryGetGeometryContractById(
|
||||
Bundle,
|
||||
Bundle.PrimaryGeometryContractId,
|
||||
GeometryContract
|
||||
)
|
||||
|| !UHyperTwistTrainingClassicCubingLibrary::TryGetViewerAdapterContractById(
|
||||
Bundle,
|
||||
Bundle.PrimaryViewerAdapterContractId,
|
||||
ViewerAdapterContract
|
||||
))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutProjection.ProjectionId = FString::Printf(TEXT("classic-cubing/%s"), *SceneContext.SceneContextId);
|
||||
OutProjection.SemanticContractId = SemanticContract.ContractId;
|
||||
OutProjection.GeometryContractId = GeometryContract.ContractId;
|
||||
OutProjection.ViewerAdapterContractId = ViewerAdapterContract.ContractId;
|
||||
OutProjection.SceneContextId = SceneContext.SceneContextId;
|
||||
OutProjection.RenderStateProfile = SceneContext.RenderStateProfile;
|
||||
OutProjection.BaseState = SceneContext.PuzzleState;
|
||||
OutProjection.CurrentDisplayMoveIndex = PlaybackState.CurrentMoveIndex;
|
||||
OutProjection.TotalTimelineTimeMs = PlaybackState.TotalTimeMs;
|
||||
OutProjection.PositionSnapshot = PositionSnapshot;
|
||||
OutProjection.ViewerSurfaceTags = ViewerAdapterContract.ViewerSurfaceTags;
|
||||
OutProjection.LifecycleHooks = ViewerAdapterContract.LifecycleHooks;
|
||||
|
||||
const TArray<int32> ProjectionBoundaryTimesMs =
|
||||
HyperTwistPuzzleViewerComponentInternal::ResolveProjectionBoundaryTimesMs(MoveList, MoveBoundaryTimesMs);
|
||||
OutProjection.ProjectedMoves.Reserve(MoveList.Num());
|
||||
|
||||
for (int32 MoveIndex = 0; MoveIndex < MoveList.Num(); ++MoveIndex)
|
||||
{
|
||||
FHyperTwistViewerAdapterProjectionStep Step;
|
||||
Step.MoveIndex = MoveIndex;
|
||||
Step.Transform = MoveList[MoveIndex].Transform;
|
||||
Step.DisplayLabel = MoveList[MoveIndex].DisplayLabel;
|
||||
Step.StartsAtMs = HyperTwistPuzzleViewerComponentInternal::ResolveProjectionMoveStartTimeMs(
|
||||
ProjectionBoundaryTimesMs,
|
||||
MoveIndex
|
||||
);
|
||||
Step.CompletesAtMs = ProjectionBoundaryTimesMs.IsValidIndex(MoveIndex)
|
||||
? ProjectionBoundaryTimesMs[MoveIndex]
|
||||
: Step.StartsAtMs;
|
||||
OutProjection.ProjectedMoves.Add(Step);
|
||||
}
|
||||
|
||||
return OutProjection.IsStructurallyValid();
|
||||
}
|
||||
|
||||
bool UHyperTwistPuzzleViewerComponent::TryBootstrapEmbeddedClassicCubingHost(
|
||||
const FHyperTwistViewerEmbeddedHostBootstrapRequest& Request,
|
||||
FHyperTwistViewerEmbeddedHostBootstrapState& OutBootstrapState
|
||||
)
|
||||
{
|
||||
OutBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
|
||||
if (!Request.IsStructurallyValid())
|
||||
{
|
||||
HostBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
return false;
|
||||
}
|
||||
|
||||
FHyperTwistViewerClassicCubingAdapterProjection Projection;
|
||||
if (!TryBuildClassicCubingAdapterProjection(Projection))
|
||||
{
|
||||
HostBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingViewerReferenceBundle BrowserViewerBundle =
|
||||
UHyperTwistTrainingViewerLibrary::BuildBundledBrowserViewerReferenceBundle();
|
||||
const FHyperTwistTrainingSpatialReferenceBundle SpatialBundle =
|
||||
UHyperTwistTrainingSpatialLibrary::BuildBundledBrowserSpatialReferenceBundle();
|
||||
|
||||
FHyperTwistTrainingViewerEmbedContract EmbedContract;
|
||||
FHyperTwistTrainingSpatialSceneContract SceneContract;
|
||||
|
||||
if (!UHyperTwistTrainingViewerLibrary::TryGetEmbedContractById(
|
||||
BrowserViewerBundle,
|
||||
BrowserViewerBundle.PrimaryEmbedContractId,
|
||||
EmbedContract
|
||||
)
|
||||
|| !UHyperTwistTrainingSpatialLibrary::TryGetSceneContractById(
|
||||
SpatialBundle,
|
||||
SpatialBundle.PrimarySceneContractId,
|
||||
SceneContract
|
||||
))
|
||||
{
|
||||
HostBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
return false;
|
||||
}
|
||||
|
||||
HostBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
HostBootstrapState.BootstrapId = FString::Printf(TEXT("%s/%s"), *Request.HostInstanceId, *Projection.SceneContextId);
|
||||
HostBootstrapState.HostInstanceId = Request.HostInstanceId;
|
||||
HostBootstrapState.AdapterProjectionId = Projection.ProjectionId;
|
||||
HostBootstrapState.ViewerAdapterContractId = Projection.ViewerAdapterContractId;
|
||||
HostBootstrapState.BrowserEmbedContractId = EmbedContract.ContractId;
|
||||
HostBootstrapState.SpatialSceneContractId = SceneContract.ContractId;
|
||||
HostBootstrapState.SceneContextId = Projection.SceneContextId;
|
||||
HostBootstrapState.RenderStateProfile = Projection.RenderStateProfile;
|
||||
HostBootstrapState.BindingMode = Request.BindingMode;
|
||||
HostBootstrapState.bReadyForEmbed = true;
|
||||
HostBootstrapState.bTimelineSurfaceBound = Request.bBindTimelineSurface;
|
||||
HostBootstrapState.bScrubberSurfaceBound = Request.bBindScrubberSurface;
|
||||
HostBootstrapState.bStatusSurfaceBound = Request.bBindStatusSurface;
|
||||
HostBootstrapState.bPointerInputEnabled = Request.bAllowPointerInput;
|
||||
HostBootstrapState.AdapterLifecycleHooks = Projection.LifecycleHooks;
|
||||
HostBootstrapState.BrowserLifecycleHookIds = EmbedContract.LifecycleHookIds;
|
||||
HostBootstrapState.SpatialLifecycleStages = SceneContract.LifecycleStages;
|
||||
HostBootstrapState.ViewerSurfaceTags = Projection.ViewerSurfaceTags;
|
||||
HostBootstrapState.SpatialEventSurfaceTags = SceneContract.EventSurfaceTags;
|
||||
|
||||
if (!HostBootstrapState.IsStructurallyValid())
|
||||
{
|
||||
HostBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
return false;
|
||||
}
|
||||
|
||||
OutBootstrapState = HostBootstrapState;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Readout
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ DECLARE_MULTICAST_DELEGATE_OneParam(
|
|||
// Holds a loaded scene context and a flat move list, and manages a pure
|
||||
// state-machine playback cursor over that list.
|
||||
//
|
||||
// Bound 2 scope: observer channels and deterministic cursor/timeline transport.
|
||||
// Adapter lifecycle is Bound 3.
|
||||
// Bound 2 adds observer channels and deterministic cursor/timeline transport.
|
||||
// Bound 3 adds adapter projection and embedded-host bootstrap surfaces.
|
||||
// Viewport rendering is Bound 4.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -104,6 +104,23 @@ public:
|
|||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Viewer")
|
||||
bool AdvancePlaybackByMs(int32 DeltaTimeMs);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Adapter and host bootstrap
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// Build a deterministic projection packet for a consumer-side classic-cubing
|
||||
// viewer adapter without taking classic-cubing ownership into this lane.
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Viewer")
|
||||
bool TryBuildClassicCubingAdapterProjection(FHyperTwistViewerClassicCubingAdapterProjection& OutProjection) const;
|
||||
|
||||
// Prepare a minimal embedded-host bootstrap state from the landed classic-
|
||||
// cubing, browser-viewer, and browser-spatial contracts.
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Viewer")
|
||||
bool TryBootstrapEmbeddedClassicCubingHost(
|
||||
const FHyperTwistViewerEmbeddedHostBootstrapRequest& Request,
|
||||
FHyperTwistViewerEmbeddedHostBootstrapState& OutBootstrapState
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Readout accessors
|
||||
// ------------------------------------------------------------------
|
||||
|
|
@ -132,6 +149,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Viewer")
|
||||
FHyperTwistViewerDirectionState GetDirectionState() const { return DirectionState; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Viewer")
|
||||
FHyperTwistViewerEmbeddedHostBootstrapState GetHostBootstrapState() const { return HostBootstrapState; }
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Delegates
|
||||
// ------------------------------------------------------------------
|
||||
|
|
@ -182,6 +202,9 @@ private:
|
|||
UPROPERTY(VisibleAnywhere, Category = "HyperTwist|Viewer")
|
||||
bool bReplayPacketLoaded = false;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, Category = "HyperTwist|Viewer")
|
||||
FHyperTwistViewerEmbeddedHostBootstrapState HostBootstrapState;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, Category = "HyperTwist|Viewer")
|
||||
TArray<int32> MoveBoundaryTimesMs;
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,13 @@ enum class EHyperTwistViewerJumpKind : uint8
|
|||
PlayWrap UMETA(DisplayName = "Play Wrap")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistViewerEmbeddedHostBindingMode : uint8
|
||||
{
|
||||
InlineCanvas UMETA(DisplayName = "Inline Canvas"),
|
||||
ScissoredView UMETA(DisplayName = "Scissored View")
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Control action — what the caller wants the viewer to do
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -342,6 +349,228 @@ struct UNREALHYPERTWIST_API FHyperTwistViewerJumpEvent
|
|||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bound 3 adapter and embedded-host bootstrap records
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct UNREALHYPERTWIST_API FHyperTwistViewerAdapterProjectionStep
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
int32 MoveIndex = INDEX_NONE;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FHyperTwistTransformation Transform;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString DisplayLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
int32 StartsAtMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
int32 CompletesAtMs = 0;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return MoveIndex >= 0
|
||||
&& Transform.IsStructurallyValid()
|
||||
&& !DisplayLabel.IsEmpty()
|
||||
&& StartsAtMs >= 0
|
||||
&& CompletesAtMs >= StartsAtMs;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct UNREALHYPERTWIST_API FHyperTwistViewerClassicCubingAdapterProjection
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString ProjectionId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString SemanticContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString GeometryContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString ViewerAdapterContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString SceneContextId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString RenderStateProfile;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FHyperTwistPuzzleState BaseState;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
int32 CurrentDisplayMoveIndex = INDEX_NONE;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
int32 TotalTimelineTimeMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FHyperTwistViewerPositionSnapshot PositionSnapshot;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FHyperTwistViewerAdapterProjectionStep> ProjectedMoves;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> ViewerSurfaceTags;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> LifecycleHooks;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
if (ProjectionId.IsEmpty()
|
||||
|| SemanticContractId.IsEmpty()
|
||||
|| GeometryContractId.IsEmpty()
|
||||
|| ViewerAdapterContractId.IsEmpty()
|
||||
|| SceneContextId.IsEmpty()
|
||||
|| RenderStateProfile.IsEmpty()
|
||||
|| !BaseState.IsStructurallyValid()
|
||||
|| !PositionSnapshot.IsStructurallyValid()
|
||||
|| TotalTimelineTimeMs < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const FHyperTwistViewerAdapterProjectionStep& Step : ProjectedMoves)
|
||||
{
|
||||
if (!Step.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct UNREALHYPERTWIST_API FHyperTwistViewerEmbeddedHostBootstrapRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString HostInstanceId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
EHyperTwistViewerEmbeddedHostBindingMode BindingMode = EHyperTwistViewerEmbeddedHostBindingMode::InlineCanvas;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bBindTimelineSurface = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bBindScrubberSurface = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bBindStatusSurface = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bAllowPointerInput = true;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !HostInstanceId.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct UNREALHYPERTWIST_API FHyperTwistViewerEmbeddedHostBootstrapState
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString BootstrapId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString HostInstanceId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString AdapterProjectionId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString ViewerAdapterContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString BrowserEmbedContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString SpatialSceneContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString SceneContextId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString RenderStateProfile;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
EHyperTwistViewerEmbeddedHostBindingMode BindingMode = EHyperTwistViewerEmbeddedHostBindingMode::InlineCanvas;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bReadyForEmbed = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bTimelineSurfaceBound = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bScrubberSurfaceBound = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bStatusSurfaceBound = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bPointerInputEnabled = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> AdapterLifecycleHooks;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> BrowserLifecycleHookIds;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> SpatialLifecycleStages;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> ViewerSurfaceTags;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> SpatialEventSurfaceTags;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
if (!bReadyForEmbed)
|
||||
{
|
||||
return BootstrapId.IsEmpty()
|
||||
&& HostInstanceId.IsEmpty()
|
||||
&& AdapterProjectionId.IsEmpty()
|
||||
&& ViewerAdapterContractId.IsEmpty()
|
||||
&& BrowserEmbedContractId.IsEmpty()
|
||||
&& SpatialSceneContractId.IsEmpty()
|
||||
&& SceneContextId.IsEmpty();
|
||||
}
|
||||
|
||||
return !BootstrapId.IsEmpty()
|
||||
&& !HostInstanceId.IsEmpty()
|
||||
&& !AdapterProjectionId.IsEmpty()
|
||||
&& !ViewerAdapterContractId.IsEmpty()
|
||||
&& !BrowserEmbedContractId.IsEmpty()
|
||||
&& !SpatialSceneContractId.IsEmpty()
|
||||
&& !SceneContextId.IsEmpty()
|
||||
&& !RenderStateProfile.IsEmpty()
|
||||
&& BrowserLifecycleHookIds.Num() > 0
|
||||
&& SpatialLifecycleStages.Num() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Runtime surface - minimal compact replay-player bundle exposed through the
|
||||
// training runtime library without moving shell ownership out of simulation.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,210 @@
|
|||
// Copyright HyperTwist, Inc. All Rights Reserved.
|
||||
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
|
||||
#include "HyperTwistSimulation/HyperTwistPuzzleViewerComponent.h"
|
||||
#include "HyperTwistSimulation/HyperTwistViewerLibrary.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
|
||||
#if WITH_AUTOMATION_TESTS
|
||||
|
||||
namespace HyperTwistTwistyBound3AdapterBootstrapTestInternal
|
||||
{
|
||||
template <typename TStruct>
|
||||
FString SerializeStructToJson(const TStruct& Value)
|
||||
{
|
||||
FString Json;
|
||||
FJsonObjectConverter::UStructToJsonObjectString(TStruct::StaticStruct(), &Value, Json, 0, 0);
|
||||
return Json;
|
||||
}
|
||||
|
||||
FHyperTwistReplayEvent MakeReplayEvent(
|
||||
const FString& EventId,
|
||||
const int32 Sequence,
|
||||
const int32 TimeMs,
|
||||
const EHyperTwistReplayEventType EventType,
|
||||
const FString& PayloadJson
|
||||
)
|
||||
{
|
||||
FHyperTwistReplayEvent Event;
|
||||
Event.EventId = EventId;
|
||||
Event.Sequence = Sequence;
|
||||
Event.TimeMs = TimeMs;
|
||||
Event.EventType = EventType;
|
||||
Event.PayloadJson = PayloadJson;
|
||||
return Event;
|
||||
}
|
||||
|
||||
FHyperTwistReplayPacket MakeReplayPacket()
|
||||
{
|
||||
const FHyperTwistPuzzleState SampleState = UHyperTwistContractLibrary::MakeSampleHyperPuzzleState();
|
||||
|
||||
FHyperTwistStateSnapshot Snapshot;
|
||||
Snapshot.SnapshotId = TEXT("snapshot-bound3");
|
||||
Snapshot.State = SampleState;
|
||||
Snapshot.DerivedHash = TEXT("snapshot-bound3-hash");
|
||||
|
||||
FHyperTwistReplayMovePayload FirstMovePayload;
|
||||
FirstMovePayload.Notation = TEXT("R");
|
||||
FirstMovePayload.TransformationRef = TEXT("classic/r");
|
||||
FirstMovePayload.Source = TEXT("unit-test");
|
||||
|
||||
FHyperTwistReplayMovePayload SecondMovePayload;
|
||||
SecondMovePayload.Notation = TEXT("U");
|
||||
SecondMovePayload.TransformationRef = TEXT("classic/u");
|
||||
SecondMovePayload.Source = TEXT("unit-test");
|
||||
|
||||
FHyperTwistReplayMovePayload ThirdMovePayload;
|
||||
ThirdMovePayload.Notation = TEXT("F'");
|
||||
ThirdMovePayload.TransformationRef = TEXT("classic/f-prime");
|
||||
ThirdMovePayload.Source = TEXT("unit-test");
|
||||
|
||||
FHyperTwistReplayPacket ReplayPacket;
|
||||
ReplayPacket.ReplayId = TEXT("replay-bound3-001");
|
||||
ReplayPacket.SessionId = TEXT("session-bound3-001");
|
||||
ReplayPacket.PuzzleDefinition = SampleState.Definition;
|
||||
ReplayPacket.CaptureMode = EHyperTwistCaptureMode::Imported;
|
||||
ReplayPacket.Events = {
|
||||
MakeReplayEvent(
|
||||
TEXT("evt-001"),
|
||||
1,
|
||||
0,
|
||||
EHyperTwistReplayEventType::StateSnapshot,
|
||||
SerializeStructToJson(Snapshot)
|
||||
),
|
||||
MakeReplayEvent(
|
||||
TEXT("evt-002"),
|
||||
2,
|
||||
0,
|
||||
EHyperTwistReplayEventType::TimerStart,
|
||||
TEXT("")
|
||||
),
|
||||
MakeReplayEvent(
|
||||
TEXT("evt-003"),
|
||||
3,
|
||||
200,
|
||||
EHyperTwistReplayEventType::Move,
|
||||
SerializeStructToJson(FirstMovePayload)
|
||||
),
|
||||
MakeReplayEvent(
|
||||
TEXT("evt-004"),
|
||||
4,
|
||||
400,
|
||||
EHyperTwistReplayEventType::Move,
|
||||
SerializeStructToJson(SecondMovePayload)
|
||||
),
|
||||
MakeReplayEvent(
|
||||
TEXT("evt-005"),
|
||||
5,
|
||||
700,
|
||||
EHyperTwistReplayEventType::Move,
|
||||
SerializeStructToJson(ThirdMovePayload)
|
||||
),
|
||||
MakeReplayEvent(
|
||||
TEXT("evt-006"),
|
||||
6,
|
||||
1000,
|
||||
EHyperTwistReplayEventType::SolveEnd,
|
||||
TEXT("")
|
||||
)
|
||||
};
|
||||
|
||||
return ReplayPacket;
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistTwistyBound3AdapterBoundaryTest,
|
||||
"HyperTwist.CleanRoom.TwistyJs.Bound3.AdapterBoundary",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistTwistyBound3AdapterBoundaryTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
UHyperTwistPuzzleViewerComponent* Viewer = NewObject<UHyperTwistPuzzleViewerComponent>();
|
||||
TestNotNull(TEXT("Viewer component instance must be created."), Viewer);
|
||||
TestTrue(
|
||||
TEXT("Replay packet load must succeed for the Bound 3 adapter test."),
|
||||
Viewer->LoadReplayPacket(HyperTwistTwistyBound3AdapterBootstrapTestInternal::MakeReplayPacket())
|
||||
);
|
||||
|
||||
Viewer->ApplyControlRequest(UHyperTwistViewerLibrary::MakePlayRequest());
|
||||
TestTrue(TEXT("Projection test playback advance should move the cursor."), Viewer->AdvancePlaybackByMs(250));
|
||||
|
||||
FHyperTwistViewerClassicCubingAdapterProjection Projection;
|
||||
TestTrue(
|
||||
TEXT("The viewer must build a consumer-side classic-cubing adapter projection."),
|
||||
Viewer->TryBuildClassicCubingAdapterProjection(Projection)
|
||||
);
|
||||
TestTrue(TEXT("The adapter projection must be structurally valid."), Projection.IsStructurallyValid());
|
||||
TestEqual(TEXT("The semantic contract id must come from the landed classic-cubing lane."), Projection.SemanticContractId, FString(TEXT("classic-cubing-semantics-stack")));
|
||||
TestEqual(TEXT("The geometry contract id must come from the landed classic-cubing lane."), Projection.GeometryContractId, FString(TEXT("classic-cubing-geometry-stack")));
|
||||
TestEqual(TEXT("The viewer adapter contract id must come from the landed classic-cubing lane."), Projection.ViewerAdapterContractId, FString(TEXT("classic-cubing-viewer-adapter-stack")));
|
||||
TestEqual(TEXT("The projection should preserve the viewer's scene context id."), Projection.SceneContextId, Viewer->GetSceneContext().SceneContextId);
|
||||
TestEqual(TEXT("The projection should preserve the viewer's total timeline time."), Projection.TotalTimelineTimeMs, 1000);
|
||||
TestEqual(TEXT("The projection should preserve the current display move index."), Projection.CurrentDisplayMoveIndex, 1);
|
||||
TestEqual(TEXT("The projection should preserve the current position snapshot time."), Projection.PositionSnapshot.CurrentTimeMs, 250);
|
||||
TestEqual(TEXT("The deterministic move projection should include all replay moves."), Projection.ProjectedMoves.Num(), 3);
|
||||
TestEqual(TEXT("The first projected step should start at 0ms."), Projection.ProjectedMoves[0].StartsAtMs, 0);
|
||||
TestEqual(TEXT("The first projected step should complete at 200ms."), Projection.ProjectedMoves[0].CompletesAtMs, 200);
|
||||
TestEqual(TEXT("The second projected step should start at 200ms."), Projection.ProjectedMoves[1].StartsAtMs, 200);
|
||||
TestEqual(TEXT("The second projected step should complete at 400ms."), Projection.ProjectedMoves[1].CompletesAtMs, 400);
|
||||
TestEqual(TEXT("The third projected step should start at 400ms."), Projection.ProjectedMoves[2].StartsAtMs, 400);
|
||||
TestEqual(TEXT("The third projected step should complete at 700ms."), Projection.ProjectedMoves[2].CompletesAtMs, 700);
|
||||
TestTrue(TEXT("The projection should surface classic-cubing viewer lifecycle hooks."), Projection.LifecycleHooks.Contains(TEXT("timeline scrubbed")));
|
||||
TestTrue(TEXT("The projection should surface classic-cubing viewer surface tags."), Projection.ViewerSurfaceTags.Contains(TEXT("TwistyPlayer embed")));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistTwistyBound3HostBootstrapTest,
|
||||
"HyperTwist.CleanRoom.TwistyJs.Bound3.HostBootstrap",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistTwistyBound3HostBootstrapTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
UHyperTwistPuzzleViewerComponent* Viewer = NewObject<UHyperTwistPuzzleViewerComponent>();
|
||||
TestNotNull(TEXT("Viewer component instance must be created."), Viewer);
|
||||
TestTrue(
|
||||
TEXT("Replay packet load must succeed for the Bound 3 bootstrap test."),
|
||||
Viewer->LoadReplayPacket(HyperTwistTwistyBound3AdapterBootstrapTestInternal::MakeReplayPacket())
|
||||
);
|
||||
|
||||
FHyperTwistViewerEmbeddedHostBootstrapRequest Request;
|
||||
Request.HostInstanceId = TEXT("compact-host-01");
|
||||
Request.BindingMode = EHyperTwistViewerEmbeddedHostBindingMode::ScissoredView;
|
||||
Request.bBindTimelineSurface = true;
|
||||
Request.bBindScrubberSurface = true;
|
||||
Request.bBindStatusSurface = true;
|
||||
Request.bAllowPointerInput = true;
|
||||
|
||||
FHyperTwistViewerEmbeddedHostBootstrapState BootstrapState;
|
||||
TestTrue(
|
||||
TEXT("The viewer must prepare a minimal embedded host bootstrap state."),
|
||||
Viewer->TryBootstrapEmbeddedClassicCubingHost(Request, BootstrapState)
|
||||
);
|
||||
TestTrue(TEXT("The bootstrap state must be structurally valid."), BootstrapState.IsStructurallyValid());
|
||||
TestEqual(TEXT("The bootstrap state should preserve the host instance id."), BootstrapState.HostInstanceId, Request.HostInstanceId);
|
||||
TestEqual(TEXT("The bootstrap state should reference the classic-cubing viewer adapter contract."), BootstrapState.ViewerAdapterContractId, FString(TEXT("classic-cubing-viewer-adapter-stack")));
|
||||
TestEqual(TEXT("The bootstrap state should reference the landed browser viewer embed contract."), BootstrapState.BrowserEmbedContractId, FString(TEXT("browser-viewer-embed")));
|
||||
TestEqual(TEXT("The bootstrap state should reference the landed browser spatial scene contract."), BootstrapState.SpatialSceneContractId, FString(TEXT("browser-spatial-scene-stack")));
|
||||
TestEqual(TEXT("The bootstrap state should preserve the selected binding mode."), BootstrapState.BindingMode, EHyperTwistViewerEmbeddedHostBindingMode::ScissoredView);
|
||||
TestTrue(TEXT("The bootstrap state should mark the embedded host ready."), BootstrapState.bReadyForEmbed);
|
||||
TestTrue(TEXT("The bootstrap state should bind the timeline surface when requested."), BootstrapState.bTimelineSurfaceBound);
|
||||
TestTrue(TEXT("The bootstrap state should bind the scrubber surface when requested."), BootstrapState.bScrubberSurfaceBound);
|
||||
TestTrue(TEXT("The bootstrap state should bind the status surface when requested."), BootstrapState.bStatusSurfaceBound);
|
||||
TestTrue(TEXT("The bootstrap state should keep pointer input enabled when requested."), BootstrapState.bPointerInputEnabled);
|
||||
TestTrue(TEXT("The bootstrap state should carry classic-cubing adapter lifecycle hooks."), BootstrapState.AdapterLifecycleHooks.Contains(TEXT("viewer ready")));
|
||||
TestTrue(TEXT("The bootstrap state should carry browser embed lifecycle hook ids."), BootstrapState.BrowserLifecycleHookIds.Contains(TEXT("hook/model-loaded")));
|
||||
TestTrue(TEXT("The bootstrap state should carry spatial scene lifecycle stages."), BootstrapState.SpatialLifecycleStages.Contains(TEXT("create canvas root")));
|
||||
TestTrue(TEXT("The bootstrap state should carry scissored-view event routing support."), BootstrapState.SpatialEventSurfaceTags.Contains(TEXT("scissored view embedding")));
|
||||
TestEqual(TEXT("The stored bootstrap state should match the returned bootstrap id."), Viewer->GetHostBootstrapState().BootstrapId, BootstrapState.BootstrapId);
|
||||
TestEqual(TEXT("The stored bootstrap state should preserve the projection id."), Viewer->GetHostBootstrapState().AdapterProjectionId, BootstrapState.AdapterProjectionId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // WITH_AUTOMATION_TESTS
|
||||
Loading…
Add table
Reference in a new issue