Skip to content
Merged
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
8 changes: 4 additions & 4 deletions Forge.Tests/Events/EventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -439,21 +439,21 @@ public void Single_handler_called_once_even_with_multiple_matching_tags()

[Fact]
[Trait("Isolation", null)]
public void Generic_raise_does_not_trigger_non_generic_handlers()
public void Generic_raise_also_triggers_non_generic_handlers_with_boxed_payload()
{
var events = new EventManager();
var eventTag = Tag.RequestTag(_tagsManager, "simple.tag");
bool nonGenericCalled = false;
object? capturedPayload = null;
bool genericCalled = false;

events.Subscribe(eventTag, _ => nonGenericCalled = true);
events.Subscribe(eventTag, data => capturedPayload = data.Payload);
events.Subscribe<int>(eventTag, _ => genericCalled = true);

// Raise generic event
events.Raise(new EventData<int> { EventTags = eventTag.GetSingleTagContainer()!, Payload = 42 });

nonGenericCalled.Should().BeFalse("non-generic handler should not be called by generic raise");
genericCalled.Should().BeTrue();
capturedPayload.Should().Be(42, "non-generic subscriptions are catch-all and receive the boxed payload");
}

[Fact]
Expand Down
20 changes: 20 additions & 0 deletions Forge.Tests/Helpers/StatescriptTestHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,26 @@ public static CueNode CreateCueNode(
node.BindInput(CueNode.TargetInput, targetPropertyName);
return node;
}

public static RaiseEventNode CreateRaiseEventNode(
StringKey eventTagPropertyName,
StringKey targetPropertyName)
{
var node = new RaiseEventNode();
node.BindInput(RaiseEventNode.EventTagInput, eventTagPropertyName);
node.BindInput(RaiseEventNode.TargetInput, targetPropertyName);
return node;
}

public static EventListenerNode CreateEventListenerNode(
StringKey eventTagPropertyName,
StringKey listenOnPropertyName)
{
var node = new EventListenerNode();
node.BindInput(EventListenerNode.EventTagInput, eventTagPropertyName);
node.BindInput(EventListenerNode.ListenOnInput, listenOnPropertyName);
return node;
}
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions Forge.Tests/Helpers/TestCueCustomParametersProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Statescript;
using Gamesmiths.Forge.Statescript.Providers;

namespace Gamesmiths.Forge.Tests.Helpers;

Expand Down
9 changes: 9 additions & 0 deletions Forge.Tests/Helpers/TestEventPayload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright © Gamesmiths Guild.

namespace Gamesmiths.Forge.Tests.Helpers;

/// <summary>
/// A test payload carried by an event in the event-node tests.
/// </summary>
/// <param name="Amount">An arbitrary value the provider builds from an input and writes back to an output.</param>
internal sealed record TestEventPayload(int Amount);
37 changes: 37 additions & 0 deletions Forge.Tests/Helpers/TestEventPayloadProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright © Gamesmiths Guild.

using Gamesmiths.Forge.Statescript;
using Gamesmiths.Forge.Statescript.Providers;

namespace Gamesmiths.Forge.Tests.Helpers;

/// <summary>
/// A bidirectional test event-payload provider: it builds a <see cref="TestEventPayload"/> from a declared
/// <c>Amount</c> input and writes the payload's amount back to a declared <c>Amount</c> output. Used by both the
/// raise-event and event-listener node tests.
/// </summary>
internal sealed class TestEventPayloadProvider : EventPayloadProvider<TestEventPayload>
{
/// <summary>
/// The name of the declared input and output this provider uses.
/// </summary>
public const string AmountKey = "Amount";

/// <inheritdoc/>
public override IReadOnlyList<EventPayloadInput> Inputs => [new EventPayloadInput(AmountKey, typeof(int))];

/// <inheritdoc/>
public override IReadOnlyList<EventPayloadOutput> Outputs => [new EventPayloadOutput(AmountKey, typeof(int))];

/// <inheritdoc/>
public override TestEventPayload CreatePayload(GraphContext graphContext, EventPayloadInputs inputs)
{
return new TestEventPayload(inputs.Get<int>(AmountKey));
}

/// <inheritdoc/>
public override void WriteOutputs(TestEventPayload payload, EventPayloadOutputs outputs)
{
outputs.Set(AmountKey, payload.Amount);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Gamesmiths.Forge.Statescript.Nodes;
using Gamesmiths.Forge.Statescript.Nodes.Action;
using Gamesmiths.Forge.Statescript.Properties;
using Gamesmiths.Forge.Statescript.Providers;
using Gamesmiths.Forge.Tags;
using Gamesmiths.Forge.Tests.Helpers;

Expand Down
154 changes: 154 additions & 0 deletions Forge.Tests/Statescript/Nodes/Action/RaiseEventNodeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright © Gamesmiths Guild.

using FluentAssertions;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Cues;
using Gamesmiths.Forge.Events;
using Gamesmiths.Forge.Statescript;
using Gamesmiths.Forge.Statescript.Nodes;
using Gamesmiths.Forge.Statescript.Nodes.Action;
using Gamesmiths.Forge.Statescript.Properties;
using Gamesmiths.Forge.Tags;
using Gamesmiths.Forge.Tests.Helpers;

using static Gamesmiths.Forge.Tests.Helpers.NodeBindings;

namespace Gamesmiths.Forge.Tests.Statescript.Nodes.Action;

public class RaiseEventNodeTests(TagsAndCuesFixture tagsAndCuesFixture) : IClassFixture<TagsAndCuesFixture>
{
private readonly TagsManager _tagsManager = tagsAndCuesFixture.TagsManager;

[Fact]
[Trait("Graph", "RaiseEvent")]
public void Raise_event_node_raises_event_with_resolved_fields()
{
var cuesManager = new CuesManager();
var target = new TestEntity(_tagsManager, cuesManager);
var source = new TestEntity(_tagsManager, cuesManager);
var eventTag = Tag.RequestTag(_tagsManager, "test.cue1");

EventData? captured = null;
target.Events.Subscribe(eventTag, data => captured = data);

var graph = new Graph();
graph.VariableDefinitions.DefineObjectVariable("eventTag", eventTag);
graph.VariableDefinitions.DefineObjectVariable<IForgeEntity>("target", target);
graph.VariableDefinitions.DefineObjectVariable<IForgeEntity>("source", source);
graph.VariableDefinitions.DefineVariable("magnitude", 25f);

RaiseEventNode node = CreateRaiseEventNode("eventTag", "target");
node.BindInput(RaiseEventNode.SourceInput, "source");
node.BindInput(RaiseEventNode.MagnitudeInput, "magnitude");
graph.AddNode(node);
graph.AddConnection(new Connection(
graph.EntryNode.OutputPorts[EntryNode.OutputPort],
node.InputPorts[ActionNode.InputPort]));

var processor = new GraphProcessor(graph);
processor.StartGraph();

captured.Should().NotBeNull();
captured!.Value.EventTags.HasTag(eventTag).Should().BeTrue();
captured.Value.Source.Should().BeSameAs(source);
captured.Value.Target.Should().BeSameAs(target);
captured.Value.EventMagnitude.Should().Be(25f);
captured.Value.Payload.Should().BeNull();
}

[Fact]
[Trait("Graph", "RaiseEvent")]
public void Raise_event_node_combines_multiple_tags_into_one_event()
{
var cuesManager = new CuesManager();
var target = new TestEntity(_tagsManager, cuesManager);
var firstTag = Tag.RequestTag(_tagsManager, "test.cue1");
var secondTag = Tag.RequestTag(_tagsManager, "test.cue2");

EventData? captured = null;
target.Events.Subscribe(firstTag, data => captured = data);

var graph = new Graph();
graph.VariableDefinitions.DefineObjectArrayVariable("eventTag", firstTag, secondTag);
graph.VariableDefinitions.DefineObjectVariable<IForgeEntity>("target", target);

RaiseEventNode node = CreateRaiseEventNode("eventTag", "target");
graph.AddNode(node);
graph.AddConnection(new Connection(
graph.EntryNode.OutputPorts[EntryNode.OutputPort],
node.InputPorts[ActionNode.InputPort]));

new GraphProcessor(graph).StartGraph();

captured.Should().NotBeNull();
captured!.Value.EventTags.HasTag(firstTag).Should().BeTrue();
captured.Value.EventTags.HasTag(secondTag).Should().BeTrue();
}

[Fact]
[Trait("Graph", "RaiseEvent")]
public void Raise_event_node_raises_on_every_target()
{
var cuesManager = new CuesManager();
var firstTarget = new TestEntity(_tagsManager, cuesManager);
var secondTarget = new TestEntity(_tagsManager, cuesManager);
var eventTag = Tag.RequestTag(_tagsManager, "test.cue1");

bool firstFired = false;
bool secondFired = false;
firstTarget.Events.Subscribe(eventTag, _ => firstFired = true);
secondTarget.Events.Subscribe(eventTag, _ => secondFired = true);

var graph = new Graph();
graph.VariableDefinitions.DefineObjectVariable("eventTag", eventTag);
graph.VariableDefinitions.DefineObjectArrayVariable<IForgeEntity>("target", firstTarget, secondTarget);

RaiseEventNode node = CreateRaiseEventNode("eventTag", "target");
graph.AddNode(node);
graph.AddConnection(new Connection(
graph.EntryNode.OutputPorts[EntryNode.OutputPort],
node.InputPorts[ActionNode.InputPort]));

new GraphProcessor(graph).StartGraph();

firstFired.Should().BeTrue();
secondFired.Should().BeTrue();
}

[Fact]
[Trait("Graph", "RaiseEvent")]
public void Raise_event_node_raises_a_typed_event_with_the_provider_payload()
{
var cuesManager = new CuesManager();
var target = new TestEntity(_tagsManager, cuesManager);
var eventTag = Tag.RequestTag(_tagsManager, "test.cue1");

// A typed subscriber receives the event only if the node raises through the typed path (with no boxing).
TestEventPayload? captured = null;
target.Events.Subscribe<TestEventPayload>(eventTag, data => captured = data.Payload);

var graph = new Graph();
graph.VariableDefinitions.DefineObjectVariable("eventTag", eventTag);
graph.VariableDefinitions.DefineObjectVariable<IForgeEntity>("target", target);
graph.VariableDefinitions.DefineObjectProperty(
"payload",
new EventPayloadResolver(
new TestEventPayloadProvider(),
new Dictionary<string, IPropertyResolver>
{
[TestEventPayloadProvider.AmountKey] = new VariantResolver(new Variant128(42), typeof(int)),
}));

RaiseEventNode node = CreateRaiseEventNode("eventTag", "target");
node.BindInput(RaiseEventNode.PayloadInput, "payload");
graph.AddNode(node);
graph.AddConnection(new Connection(
graph.EntryNode.OutputPorts[EntryNode.OutputPort],
node.InputPorts[ActionNode.InputPort]));

new GraphProcessor(graph).StartGraph();

captured.Should().NotBeNull();
captured!.Amount.Should().Be(42);
}
}
1 change: 1 addition & 0 deletions Forge.Tests/Statescript/Nodes/State/EffectNodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Gamesmiths.Forge.Statescript.Nodes;
using Gamesmiths.Forge.Statescript.Nodes.State;
using Gamesmiths.Forge.Statescript.Properties;
using Gamesmiths.Forge.Statescript.Providers;
using Gamesmiths.Forge.Tags;
using Gamesmiths.Forge.Tests.Helpers;

Expand Down
Loading
Loading