Skip to main content
added 22 characters in body
Source Link
class RadioCommunicationState
{
    public RadioCommand CurrentCommand { get; set; }
}

class RadioCommandPerceptor : IPerceptor
{
    public RadioCommandPerceptor(IRemoteRadioCommandSource source, RadioCommunicationState radioState) { /*...*/ }
    public void Update() => _radioState.CurrentCommand = _source.RecentCommands.LastOrDefault();
}

class HoldPositionRule : IRule
{
    public HoldPositionRule(RadioCommunicationState radioState) { /*...*/ }
    public float PriorityScale { get; set; } = 42.314f; // for fine-tuning, read it from the config
    public float RateSelf() => (_radioState.CurrentCommand == RadioCommand.Hold) ? 1f : 0f;
    public void Apply() => controls_vehicleControlsState.Brake();DesiredSpeed = 0;
}

class VehicleAgent : IAgent
{
    public void Update()
    {
        foreach (var perceptor in _perceptors)
            perceptor.Update();
        var rule = _rules.Max(r => r.RateSelf() * r.PriorityScale);
        rule.Apply();
    }
}

class Program
{
    static VehicleAgent BuildVehicleAgent()
    {
        var collection = new ServiceCollection();
        collection.AddSingleton<RadioCommunicationState>();
        collection.AddSingleton<RadioCommandPerceptor>();
        collection.AddSingleton<HoldPositionRule>();
        collection.AddSingleton<VehicleAgent>();
        // ...
        return collection.BuildServiceProvider().GetService<VehicleAgent>();
    }
}
class RadioCommunicationState
{
    public RadioCommand CurrentCommand { get; set; }
}

class RadioCommandPerceptor : IPerceptor
{
    public RadioCommandPerceptor(IRemoteRadioCommandSource source, RadioCommunicationState radioState) { /*...*/ }
    public void Update() => _radioState.CurrentCommand = _source.RecentCommands.LastOrDefault();
}

class HoldPositionRule : IRule
{
    public HoldPositionRule(RadioCommunicationState radioState) { /*...*/ }
    public float PriorityScale { get; set; } = 42.314f; // for fine-tuning, read it from the config
    public float RateSelf() => (_radioState.CurrentCommand == RadioCommand.Hold) ? 1f : 0f;
    public void Apply() => controls.Brake();
}

class VehicleAgent : IAgent
{
    public void Update()
    {
        foreach (var perceptor in _perceptors)
            perceptor.Update();
        var rule = _rules.Max(r => r.RateSelf() * r.PriorityScale);
        rule.Apply();
    }
}

class Program
{
    static VehicleAgent BuildVehicleAgent()
    {
        var collection = new ServiceCollection();
        collection.AddSingleton<RadioCommunicationState>();
        collection.AddSingleton<RadioCommandPerceptor>();
        collection.AddSingleton<HoldPositionRule>();
        collection.AddSingleton<VehicleAgent>();
        // ...
        return collection.BuildServiceProvider().GetService<VehicleAgent>();
    }
}
class RadioCommunicationState
{
    public RadioCommand CurrentCommand { get; set; }
}

class RadioCommandPerceptor : IPerceptor
{
    public RadioCommandPerceptor(IRemoteRadioCommandSource source, RadioCommunicationState radioState) { /*...*/ }
    public void Update() => _radioState.CurrentCommand = _source.RecentCommands.LastOrDefault();
}

class HoldPositionRule : IRule
{
    public HoldPositionRule(RadioCommunicationState radioState) { /*...*/ }
    public float PriorityScale { get; set; } = 42.314f; // for fine-tuning, read it from the config
    public float RateSelf() => (_radioState.CurrentCommand == RadioCommand.Hold) ? 1f : 0f;
    public void Apply() => _vehicleControlsState.DesiredSpeed = 0;
}

class VehicleAgent : IAgent
{
    public void Update()
    {
        foreach (var perceptor in _perceptors)
            perceptor.Update();
        var rule = _rules.Max(r => r.RateSelf() * r.PriorityScale);
        rule.Apply();
    }
}

class Program
{
    static VehicleAgent BuildVehicleAgent()
    {
        var collection = new ServiceCollection();
        collection.AddSingleton<RadioCommunicationState>();
        collection.AddSingleton<RadioCommandPerceptor>();
        collection.AddSingleton<HoldPositionRule>();
        collection.AddSingleton<VehicleAgent>();
        // ...
        return collection.BuildServiceProvider().GetService<VehicleAgent>();
    }
}
added 1186 characters in body
Source Link

IRemoteRadioCommandSource is just an abstraction to plug few holes in my examples. It does not matter that much where a command comes from. But I presume all commands go through the same radio channel, so putting all the commands into the same source makes more sense. (And

And while we're here, let's change perceptor to interpret a command via an intermediate state. This way more perceptors may be added without touching the rules.)

Also, with the Finite-state machine approach only one rule must be selected and applied at a time, correct? So, in our example, if both rules are "valid" (both a RadioCommand.Hold and a NewRoute exist), onle one of these will happen during the first Update cycle and the other one during the next cycle. Have I understood this correctly?

It's always the rule with the highest weight. You want to tweak the rules carefully to ensure that there is no stalemate, otherwise the behavior will be dependent on the phase of the Moon. But again, you don't need to rely on a single FSM, we prefer them because they are cheap and composable.

Commands are confusing, because they are more similar to events ("I saw a cat") rather than perceptible state ("True, I see a red traffic light right now"). Moving command handling into perceptors (as demonstrated in the code above) may solve this conceptual misalignment.

IRemoteRadioCommandSource is just an abstraction to plug few holes in my examples. It does not matter that much where a command comes from. But I presume all commands go through the same radio channel, so putting all the commands into the same source makes more sense. (And while we're here, let's change perceptor to interpret a command via an intermediate state. This way more perceptors may be added without touching the rules.)

IRemoteRadioCommandSource is just an abstraction to plug few holes in my examples. It does not matter that much where a command comes from. But I presume all commands go through the same radio channel, so putting all the commands into the same source makes more sense.

And while we're here, let's change perceptor to interpret a command via an intermediate state. This way more perceptors may be added without touching the rules.

Also, with the Finite-state machine approach only one rule must be selected and applied at a time, correct? So, in our example, if both rules are "valid" (both a RadioCommand.Hold and a NewRoute exist), onle one of these will happen during the first Update cycle and the other one during the next cycle. Have I understood this correctly?

It's always the rule with the highest weight. You want to tweak the rules carefully to ensure that there is no stalemate, otherwise the behavior will be dependent on the phase of the Moon. But again, you don't need to rely on a single FSM, we prefer them because they are cheap and composable.

Commands are confusing, because they are more similar to events ("I saw a cat") rather than perceptible state ("True, I see a red traffic light right now"). Moving command handling into perceptors (as demonstrated in the code above) may solve this conceptual misalignment.

added 1186 characters in body
Source Link

If there was another possibility of a perceptor making the vehicle agents to change their routes, would I have a IRouteAssignmentCommandSource injected in a RouteAssignmentCommandPerceptor

IRemoteRadioCommandSource is just an abstraction to plug few holes in my examples. It does not matter that much where a command comes from. But I presume all commands go through the same radio channel, so putting all the commands into the same source makes more sense. (And while we're here, let's change perceptor to interpret a command via an intermediate state. This way more perceptors may be added without touching the rules.)

class TemporaryStopIntentState
{
    public DateTime Target { get; init; }
}

class HoldPositionCommand
{
    public DateTime ReleaseTime { get; init; }
}

class RadioCommandPerceptor : IPerceptor
{
    /* ... */
    public void Update()
    {
        if (_source.PendingCommand is HoldCommand command)
            _temporaryStopIntentState.Target = command.ReleaseTime;
    }
}

class HoldPositionRule : IRule
{
    /* ... */
    public float RateSelf() => (_temporaryStopIntentState.Target > DateTime.Now) ? 1f : 0f;
}

If there was another possibility of a perceptor making the vehicle agents to change their routes, would I have a IRouteAssignmentCommandSource injected in a RouteAssignmentCommandPerceptor

IRemoteRadioCommandSource is just an abstraction to plug few holes in my examples. It does not matter that much where a command comes from. But I presume all commands go through the same radio channel, so putting all the commands into the same source makes more sense. (And while we're here, let's change perceptor to interpret a command via an intermediate state. This way more perceptors may be added without touching the rules.)

class TemporaryStopIntentState
{
    public DateTime Target { get; init; }
}

class HoldPositionCommand
{
    public DateTime ReleaseTime { get; init; }
}

class RadioCommandPerceptor : IPerceptor
{
    /* ... */
    public void Update()
    {
        if (_source.PendingCommand is HoldCommand command)
            _temporaryStopIntentState.Target = command.ReleaseTime;
    }
}

class HoldPositionRule : IRule
{
    /* ... */
    public float RateSelf() => (_temporaryStopIntentState.Target > DateTime.Now) ? 1f : 0f;
}
Source Link
Loading