Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Test/LingoEngine.Lingo.Tests/MovieEventOrderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Linq;
using LingoEngine.Events;
using LingoEngine.Lingo.Tests.TestDoubles;
using Xunit;

namespace LingoEngine.Lingo.Tests;

public class MovieEventOrderTests
{
[Fact]
public void AdvanceFrame_RaisesFrameLifecycleEventsInManualOrder()
{
var timeline = new List<string>();
var mediator = new LingoEventMediator();
var frameHandler = new RecordingFrameHandler(timeline);
mediator.Subscribe(frameHandler);
mediator.SubscribeStepFrame(frameHandler);

var harness = FakeLingoMovieBuilder.Create(mediator, timeline);
PrivateFieldSetter.SetField(harness.Movie, "_isPlaying", true);

harness.Movie.AdvanceFrame();
harness.Movie.OnIdle(1f / 60f);
harness.Movie.AdvanceFrame();

var expected = new[]
{
"beginSprite",
"stepFrame",
"prepareFrame",
"enterFrame",
"idleFrame",
"exitFrame",
"endSprite"
};

Assert.True(timeline.Count >= expected.Length, "timeline missing expected callbacks");
Assert.Equal(expected, timeline.Take(expected.Length));
}
}
207 changes: 207 additions & 0 deletions Test/LingoEngine.Lingo.Tests/PuppetSpriteLifecycleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using LingoEngine.Events;
using LingoEngine.Lingo.Tests.TestDoubles;
using LingoEngine.Members;
using LingoEngine.Movies;
using LingoEngine.Sprites;
using AbstUI.Components;
using AbstUI.Primitives;
using Xunit;

namespace LingoEngine.Lingo.Tests;

public sealed class PuppetSpriteLifecycleTests
{
[Fact]
public void UpdateActiveSprites_RemovesTimelineSpriteWhenPuppetNotSet()
{
var harness = TestHarness.Create();
var sprite = harness.CreateSprite(spriteNum: 1, beginFrame: 1, endFrame: 1);
sprite.IsActive = true;
harness.Manager.TrackActiveSprite(sprite);

harness.Manager.UpdateActiveSprites(currentFrame: 2, lastFrame: 1);

Assert.DoesNotContain(sprite, harness.Manager.ActiveSprites);
Assert.Contains(sprite, harness.Manager.ExitedSprites);
Assert.False(sprite.IsActive);
}

[Fact]
public void UpdateActiveSprites_PreservesSpriteWhenPuppetTrue()
{
var harness = TestHarness.Create();
var sprite = harness.CreateSprite(spriteNum: 1, beginFrame: 1, endFrame: 1);
sprite.IsActive = true;
sprite.Puppet = true;
harness.Manager.TrackActiveSprite(sprite);

harness.Manager.UpdateActiveSprites(currentFrame: 2, lastFrame: 1);

Assert.Contains(sprite, harness.Manager.ActiveSprites);
Assert.DoesNotContain(sprite, harness.Manager.ExitedSprites);
Assert.True(sprite.IsActive);
}

private sealed class TestHarness
{
private TestHarness(TestSpriteManager manager, LingoEventMediator mediator)
{
Manager = manager;
Mediator = mediator;
}

internal TestSpriteManager Manager { get; }
private LingoEventMediator Mediator { get; }

internal static TestHarness Create()
{
var mediator = new LingoEventMediator();
var manager = TestSpriteManager.Create();
return new TestHarness(manager, mediator);
}

internal TestSprite CreateSprite(int spriteNum, int beginFrame, int endFrame)
{
var sprite = new TestSprite(Mediator);
sprite.Initialize(spriteNum, beginFrame, endFrame);
return sprite;
}
}

private sealed class TestSpriteManager : LingoSpriteManager<TestSprite>
{
private TestSpriteManager() : base(0, null!, null!)
{
}

internal static TestSpriteManager Create()
{
var manager = (TestSpriteManager)FormatterServices.GetUninitializedObject(typeof(TestSpriteManager));
manager.Initialize();
return manager;
}

private void Initialize()
{
PrivateFieldSetter.SetField(this, "_mutedSprites", new List<int>());
PrivateFieldSetter.SetField(this, "_movie", (LingoMovie)FormatterServices.GetUninitializedObject(typeof(LingoMovie)));
PrivateFieldSetter.SetField(this, "_environment", null);
PrivateFieldSetter.SetField(this, "<SpriteNumChannelOffset>k__BackingField", 0);
PrivateFieldSetter.SetField(this, "_spriteChannels", new Dictionary<int, LingoSpriteChannel>());
PrivateFieldSetter.SetField(this, "_spritesByName", new Dictionary<string, TestSprite>());
PrivateFieldSetter.SetField(this, "_allTimeSprites", new List<TestSprite>());
PrivateFieldSetter.SetField(this, "_newPuppetSprites", new List<TestSprite>());
PrivateFieldSetter.SetField(this, "_activeSprites", new Dictionary<int, TestSprite>());
PrivateFieldSetter.SetField(this, "_activeSpritesOrdered", new List<TestSprite>());
PrivateFieldSetter.SetField(this, "_enteredSprites", new List<TestSprite>());
PrivateFieldSetter.SetField(this, "_exitedSprites", new List<TestSprite>());
PrivateFieldSetter.SetField(this, "_deletedPuppetSpritesCache", new Dictionary<int, TestSprite>());
}

protected override TestSprite OnCreateSprite(LingoMovie movie, Action<TestSprite> onRemove) => throw new NotSupportedException();

protected override LingoSprite? OnAdd(int spriteNum, int begin, int end, ILingoMember? member) => null;

protected override void SpriteEntered(TestSprite sprite)
{
}

protected override void SpriteExited(TestSprite sprite)
{
}

internal void TrackActiveSprite(TestSprite sprite)
{
_allTimeSprites.Add(sprite);
_activeSprites[sprite.SpriteNum] = sprite;
_activeSpritesOrdered.Add(sprite);
}

internal IReadOnlyCollection<TestSprite> ActiveSprites => _activeSprites.Values;
internal IReadOnlyCollection<TestSprite> ExitedSprites => _exitedSprites;
}

private sealed class TestSprite : LingoSprite
{
private readonly StubFrameworkSprite _stubFrameworkSprite;

internal TestSprite(LingoEventMediator mediator) : base(mediator)
{
_stubFrameworkSprite = new StubFrameworkSprite();
PrivateFieldSetter.SetField(this, "_frameworkSprite", _stubFrameworkSprite);
}

public override int SpriteNumWithChannel => SpriteNum;

internal void Initialize(int spriteNum, int beginFrame, int endFrame)
{
Init(spriteNum, $"Sprite_{spriteNum}");
BeginFrame = beginFrame;
EndFrame = endFrame;
}

public override void OnRemoveMe()
{
}
}

private sealed class StubFrameworkSprite : ILingoFrameworkSprite
{
public string Name { get; set; } = string.Empty;
public bool Visibility { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public AMargin Margin { get; set; }
public int ZIndex { get; set; }
public object FrameworkNode => this;
public float X { get; set; }
public float Y { get; set; }
public float Blend { get; set; }
public APoint RegPoint { get; set; }
public float DesiredHeight { get; set; }
public float DesiredWidth { get; set; }
public float Rotation { get; set; }
public float Skew { get; set; }
public bool FlipH { get; set; }
public bool FlipV { get; set; }
public bool DirectToStage { get; set; }
public int Ink { get; set; }

public void Dispose()
{
}

public void Hide()
{
}

public void MemberChanged()
{
}

public void RemoveMe()
{
}

public void SetPosition(APoint point)
{
X = point.X;
Y = point.Y;
}

public void SetTexture(IAbstTexture2D texture)
{
}

public void Show()
{
}

public void ApplyMemberChangesOnStepFrame()
{
}
}
}
29 changes: 29 additions & 0 deletions Test/LingoEngine.Lingo.Tests/TestDoubles/FakeFrameworkMovie.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using AbstUI.Primitives;
using LingoEngine.Movies;

namespace LingoEngine.Lingo.Tests.TestDoubles;

internal sealed class FakeFrameworkMovie : ILingoFrameworkMovie
{
public string Name { get; set; } = "TestFrameworkMovie";
public bool Visibility { get; set; } = true;
public float Width { get; set; } = 640f;
public float Height { get; set; } = 480f;
public AMargin Margin { get; set; } = AMargin.Zero;
public int ZIndex { get; set; }
public object FrameworkNode => this;

public void Dispose()
{
}

public APoint GetGlobalMousePosition() => (0, 0);

public void RemoveMe()
{
}

public void UpdateStage()
{
}
}
90 changes: 90 additions & 0 deletions Test/LingoEngine.Lingo.Tests/TestDoubles/FakeLingoMovieBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using LingoEngine.Core;
using LingoEngine.Events;
using LingoEngine.Inputs;
using LingoEngine.Movies;
using LingoEngine.Sprites;

namespace LingoEngine.Lingo.Tests.TestDoubles;

internal static class FakeLingoMovieBuilder
{
internal static FakeLingoMovieHarness Create(LingoEventMediator mediator, List<string> timeline, Action<FakeLingoMovieOptions>? configure = null)
{
var options = new FakeLingoMovieOptions();
configure?.Invoke(options);

var movie = (LingoMovie)FormatterServices.GetUninitializedObject(typeof(LingoMovie));

var spriteManagers = new List<LingoSpriteManager>();
PrivateFieldSetter.SetField(movie, "_spriteManagers", spriteManagers);
PrivateFieldSetter.SetField(movie, "_eventMediator", mediator);
PrivateFieldSetter.SetField(movie, "_actorList", new ActorList());
PrivateFieldSetter.SetField(movie, "_idleHandlerPeriod", 1);
PrivateFieldSetter.SetField(movie, "_idleIntervalSeconds", 1f / 60f);
PrivateFieldSetter.SetField(movie, "_currentFrame", 0);
PrivateFieldSetter.SetField(movie, "_lastFrame", 0);
PrivateFieldSetter.SetField(movie, "_nextFrame", -1);
PrivateFieldSetter.SetField(movie, "_needToRaiseStartMovie", false);
PrivateFieldSetter.SetField(movie, "_hasPendingEndSprites", false);
PrivateFieldSetter.SetField(movie, "_frameIsActive", false);
PrivateFieldSetter.SetField(movie, "_idleAccumulator", 0f);

var mouse = (LingoStageMouse)FormatterServices.GetUninitializedObject(typeof(LingoStageMouse));
PrivateFieldSetter.SetField(movie, "_lingoMouse", mouse);

var frameworkMovie = new FakeFrameworkMovie();
PrivateFieldSetter.SetField(movie, "_frameworkMovie", frameworkMovie);

var sprite2DManager = FakeSprite2DManager.Create(movie, mouse, timeline);
PrivateFieldSetter.SetField(movie, "_sprite2DManager", sprite2DManager);

var transitionManager = FakeTransitionManager.Create(movie, mediator, timeline);
PrivateFieldSetter.SetField(movie, "_transitionManager", transitionManager);

var transitionPlayer = options.TransitionPlayer ?? new FakeTransitionPlayer(
options.TransitionStartLabel != null ? timeline : null,
options.TransitionStartLabel);
PrivateFieldSetter.SetField(movie, "_transitionPlayer", transitionPlayer);

if (options.RecordTransitionLifecycle)
{
transitionManager.IsLifecycleRecordingEnabled = true;
transitionManager.SetActivationFrame(options.TransitionActivationFrame);
spriteManagers.Add(transitionManager);
}
else
{
transitionManager.IsLifecycleRecordingEnabled = false;
transitionManager.SetActivationFrame(int.MaxValue);
}

return new FakeLingoMovieHarness(movie, sprite2DManager, transitionManager, transitionPlayer);
}
}

internal sealed class FakeLingoMovieOptions
{
internal bool RecordTransitionLifecycle { get; set; }
internal int TransitionActivationFrame { get; set; } = 1;
internal FakeTransitionPlayer? TransitionPlayer { get; set; }
internal string? TransitionStartLabel { get; set; }
}

internal sealed class FakeLingoMovieHarness
{
internal FakeLingoMovieHarness(LingoMovie movie, FakeSprite2DManager sprite2DManager, FakeTransitionManager transitionManager, FakeTransitionPlayer transitionPlayer)
{
Movie = movie;
Sprite2DManager = sprite2DManager;
TransitionManager = transitionManager;
TransitionPlayer = transitionPlayer;
}

internal LingoMovie Movie { get; }
internal FakeSprite2DManager Sprite2DManager { get; }
internal FakeTransitionManager TransitionManager { get; }
internal FakeTransitionPlayer TransitionPlayer { get; }
}
Loading