Add machine workshop, typed logic, mechanics and sensor-driven ECUs

This commit is contained in:
max
2026-11-02 07:27:13 +01:00
parent eb2b6366c2
commit abfcce0f46
99 changed files with 28835 additions and 145 deletions

View File

@@ -0,0 +1,111 @@
#pragma once
#include "FluidSim/MachineSensors.hpp"
#include "FluidSim/Ecu.hpp"
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>
namespace fluidsim::port {
struct MachineDocument;
enum class LogicNodeKind {
BooleanConstant,Switch,Button,NumberConstant,Slider,NumberInput,
And,Or,Not,Xor,Nand,Nor,Greater,Less,Equal,Window,Hysteresis,
Add,Subtract,Multiply,Divide,Minimum,Maximum,Absolute,Clamp,GainOffset,MapRange,Select,BooleanToNumber,
Sensor,Command,Lamp,Readout,BooleanDelay,NumberDelay,Ecu
};
inline constexpr int logic_node_kind_count=36;
enum class LogicCommand {MotorEnable,MotorSpeed,MotorTorque,BrakeEnable,ThrottleOpening,Starter};
inline constexpr int logic_command_count=6;
struct LogicPosition {float x{},y{};bool operator==(const LogicPosition&)const=default;};
struct LogicNode {
std::uint32_t id{};
LogicNodeKind kind{LogicNodeKind::Switch};
std::string name;
LogicPosition position;
SignalQuantity quantity{SignalQuantity::Scalar},output_quantity{SignalQuantity::Scalar};
PressureReference pressure_reference{PressureReference::None},output_pressure_reference{PressureReference::None};
double value{},minimum{},maximum{1},low{.25},high{.75},tolerance{1e-6};
double gain{1},offset{},false_value{},true_value{1},input_low{},input_high{1},output_low{},output_high{1};
bool boolean{},initial{},below{},clamp{true};
unsigned input_count{2};
std::uint32_t sensor{},part{};
MachineSensorKind sensor_kind{MachineSensorKind::Pressure}; // Schema survives an unresolved reference.
SensorEcuConfig::Strategy ecu_strategy{SensorEcuConfig::Strategy::Ignition};
LogicCommand command{LogicCommand::MotorEnable};
std::string binding_label;
bool operator==(const LogicNode&)const=default;
};
struct LogicEndpoint {
std::uint32_t node{};
std::string port;
bool operator==(const LogicEndpoint&)const=default;
};
struct LogicLink {
LogicEndpoint from,to;
bool operator==(const LogicLink&)const=default;
};
struct LogicGraph {
std::uint32_t next_id{1};
std::vector<LogicNode> nodes;
std::vector<LogicLink> links;
LogicNode* find(std::uint32_t);
const LogicNode* find(std::uint32_t)const;
LogicNode& add(LogicNodeKind,LogicPosition={});
void remove(std::uint32_t);
void disconnect(const LogicEndpoint&);
void connect(LogicEndpoint,LogicEndpoint); // Canonical output -> input; validates without mutation on failure.
bool operator==(const LogicGraph&)const=default;
};
const char* logic_node_key(LogicNodeKind);
const char* logic_node_name(LogicNodeKind);
const char* logic_command_key(LogicCommand);
const char* logic_command_name(LogicCommand);
bool logic_memory_node(LogicNodeKind);
std::vector<MachinePortDescriptor> logic_node_ports(const LogicNode&,const MachineDocument* context=nullptr);
MachinePortDescriptor logic_port(const LogicGraph&,const LogicEndpoint&);
PortCompatibility logic_connection_preview(const LogicGraph&,LogicEndpoint,LogicEndpoint);
void validate_logic_graph(const LogicGraph&); // Drafts may have required unconnected inputs.
std::vector<std::uint32_t> logic_evaluation_order(const LogicGraph&,std::vector<std::uint32_t>* cycle_path=nullptr);
std::vector<std::string> logic_required_inputs(const LogicGraph&);
bool logic_topology_equal(const LogicGraph&,const LogicGraph&);
// Empty means a compatible physical binding. Missing targets remain unresolved.
std::string logic_binding_problem(const MachineDocument&,const LogicNode&);
const LogicNode* logic_command_owner(const MachineDocument&,std::uint32_t part,LogicCommand);
void validate_machine_logic(const MachineDocument&);
struct LogicNodeState {
std::uint32_t id{};
std::vector<SignalValue> values; // Same order as logic_node_ports, including inputs.
std::string problem;
};
struct LogicCommandState {
std::uint32_t node{},part{};
LogicCommand command{};
SignalValue value; // Invalid values require the declared inactive consumer fallback.
};
struct LogicEcuInputs {
std::uint32_t node{},part{};
SignalValue speed,angle,tps,map,iat;
};
class MachineLogicRuntime {
public:
using SensorReader=std::function<MachineSensorState(std::uint32_t)>;
explicit MachineLogicRuntime(const MachineDocument&,SensorReader={});
~MachineLogicRuntime();
MachineLogicRuntime(const MachineLogicRuntime&)=delete;
MachineLogicRuntime& operator=(const MachineLogicRuntime&)=delete;
void evaluate(double time,double dt);
SignalValue value(const LogicEndpoint&)const;
std::vector<LogicNodeState> snapshot()const;
const std::vector<LogicCommandState>& commands()const;
const std::vector<LogicEcuInputs>& ecu_inputs()const;
void set_manual(std::uint32_t,bool);
void set_manual(std::uint32_t,double);
void release_buttons();
void apply_tuning(const MachineDocument&);
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
}