FastMM documentation
FastMM C++ API 0.6.0
Public headers, tier 1 and tier 2
Loading...
Searching...
No Matches
param_publisher.hpp
Go to the documentation of this file.
1#pragma once
2// ParamPublisher: builds ParamUpdate messages for a running engine, off the engine thread
3// (ADR-0013).
4//
5// MsgRing ring(1U << 16); // one of the engine's RingFeed rings
6// ParamPublisher pub(ParamSink::to_ring(ring), strategy.params());
7// pub.publish({{"half_spread_bps", "7.5"}}); // false: no room in the ring
8//
9// A publish parses the named values into a copy of the parameters with the schema's parsers and
10// ranges, then runs the params struct's validate(). An unknown name, a name given twice, a value
11// that does not parse or is out of range, a failed validate() and more than
12// ParamUpdateMsg::kMaxFields values throw std::invalid_argument; nothing is sent then. The engine
13// copies the raw values without checks.
14//
15// The copy starts from the parameters passed to the constructor (read them before the engine thread
16// starts) and follows every update that was sent, so it equals what the engine holds once it has
17// consumed the ring. Parameters an update does not name keep their values. With `instruments` > 0
18// the publisher keeps one copy per instrument and an update may name one instrument; a strategy
19// that keeps one parameter set (StrategyBase) publishes to all instruments.
20//
21// Calls may come from any thread; a mutex serialises them, so the publisher is the ring's only
22// producer. Not for the engine thread: publish allocates.
24#include "fastmm/core/msg_ring.hpp"
26#include "fastmm/core/time.hpp"
28
29#include <cstddef>
30#include <cstdint>
31#include <memory>
32#include <mutex>
33#include <optional>
34#include <stdexcept>
35#include <string>
36#include <string_view>
37#include <utility>
38#include <vector>
39
40namespace fastmm {
41
42// Where a built update goes; push returns false when it was not accepted (full).
43struct ParamSink {
44 void* ctx = nullptr;
45 bool (*push)(void* ctx, const ParamUpdateMsg& m) noexcept = nullptr;
47 // The SPSC ring an engine's RingFeed polls.
48 [[nodiscard]] static ParamSink to_ring(MsgRing& ring) noexcept {
49 return {&ring, [](void* c, const ParamUpdateMsg& m) noexcept {
50 return static_cast<MsgRing*>(c)->try_push(&m.hdr, m.hdr.len);
51 }};
52 }
53};
54
55class ParamPublisher {
56 public:
57 using ParamValue = std::pair<std::string, std::string>; // name, value as ParamDesc::parse takes
60 // Copying and freeing a parameter block whose layout is known only at run time
61 // (strategies/hot_params.hpp); validate runs after the schema's parsers.
62 struct BlockOps {
63 void* (*clone)(const void* src);
64 void (*destroy)(void* p) noexcept;
65 std::optional<std::string> (*validate)(const void* p);
66 };
67
68 template <class Params>
69 ParamPublisher(ParamSink sink, const Params& current, std::size_t instruments = 0)
70 : ParamPublisher(sink, Params::schema(), block_ops<Params>(), &current, instruments) {}
71 // `schema`'s parsers and `ops` work on blocks like `current`; `schema` must outlive the
72 // publisher.
74 const ParamSchema& schema,
75 const BlockOps& ops,
76 const void* current,
77 std::size_t instruments = 0)
78 : sink_(sink), schema_(&schema), ops_(ops), per_instrument_(instruments != 0) {
79 const std::size_t n = instruments == 0 ? 1 : instruments;
80 blocks_.reserve(n);
81 for (std::size_t i = 0; i < n; ++i) blocks_.push_back(clone(current));
82 }
83 ParamPublisher(const ParamPublisher&) = delete;
84 ParamPublisher& operator=(const ParamPublisher&) = delete;
87 ~ParamPublisher() = default;
89 // Validates and sends one update. Throws std::invalid_argument when it is invalid; returns false
90 // when the sink refused it (ring full) or the publisher is closed.
91 bool publish(const std::vector<ParamValue>& values, InstrumentId inst = kAllInstruments) {
93 }
94 // publish() with the update's origin and source (ParamUpdateMsg::source, cut to 39 bytes).
95 // The update's publish_seq, 0 when the sink refused it or the publisher is closed.
96 std::uint64_t publish_as(const std::vector<ParamValue>& values,
97 InstrumentId inst,
99 std::string_view source) {
100 const std::lock_guard<std::mutex> lock(mutex_);
101 if (closed_) return 0;
102 ParamUpdateMsg m{};
103 std::vector<std::pair<std::size_t, Block>> next;
104 if (auto err = build_locked(values, inst, m, &next)) throw std::invalid_argument(*err);
105 m.publish_seq = seq_ + 1;
106 m.origin = origin;
107 m.set_source(source);
108 m.hdr.recv_ts = wall_now();
109 if (!sink_.push(sink_.ctx, m)) {
110 ++refused_;
111 return 0;
112 }
113 ++seq_;
114 for (auto& [k, block] : next) blocks_[k] = std::move(block);
115 return seq_;
116 }
117
118 // The update publish() would send, without sending it or changing the copy; the error message
119 // when it is invalid. `out.publish_seq` and `out.hdr.recv_ts` stay zero.
120 [[nodiscard]] std::optional<std::string> build(const std::vector<ParamValue>& values,
121 InstrumentId inst,
122 ParamUpdateMsg& out) const {
123 const std::lock_guard<std::mutex> lock(mutex_);
124 return build_locked(values, inst, out, nullptr);
125 }
126
127 // Later publishes return false (the session has stopped).
128 void close() {
129 const std::lock_guard<std::mutex> lock(mutex_);
130 closed_ = true;
131 }
132 [[nodiscard]] bool closed() const {
133 const std::lock_guard<std::mutex> lock(mutex_);
134 return closed_;
135 }
136 // Updates sent, and updates the sink refused.
137 [[nodiscard]] std::uint64_t published() const {
138 const std::lock_guard<std::mutex> lock(mutex_);
139 return seq_;
140 }
141 [[nodiscard]] std::uint64_t refused() const {
142 const std::lock_guard<std::mutex> lock(mutex_);
143 return refused_;
144 }
145 [[nodiscard]] const ParamSchema& schema() const { return *schema_; }
146 // Every parameter of the copy, in schema order: one instrument's, or the shared set.
147 struct Value {
148 std::string name;
150 std::int64_t raw; // ParamDesc::get_raw
151 std::string text; // ParamDesc::format
152 };
153 [[nodiscard]] std::vector<Value> values(InstrumentId inst = kAllInstruments) const {
154 const std::lock_guard<std::mutex> lock(mutex_);
155 const std::size_t k =
156 per_instrument_ && inst.valid() && inst.value < blocks_.size() ? inst.value : 0;
157 std::vector<Value> out;
158 out.reserve(schema_->size());
159 for (const ParamDesc& d : *schema_)
160 out.push_back({d.name, d.type, d.get_raw(blocks_[k].get()), d.format(blocks_[k].get())});
161 return out;
162 }
163 // `name=value` pairs of the copy: one instrument's, or the shared set.
164 [[nodiscard]] std::string describe(InstrumentId inst = kAllInstruments) const {
165 const std::lock_guard<std::mutex> lock(mutex_);
166 const std::size_t k =
167 per_instrument_ && inst.valid() && inst.value < blocks_.size() ? inst.value : 0;
168 return detail::describe_params(*schema_, blocks_[k].get());
169 }
170
171 private:
172 template <class P>
173 static const BlockOps& block_ops() {
174 static constexpr BlockOps kOps{
175 [](const void* src) -> void* { return new P(*static_cast<const P*>(src)); },
176 [](void* p) noexcept { delete static_cast<P*>(p); },
177 [](const void* p) { return detail::validate_params(*static_cast<const P*>(p)); }};
178 return kOps;
179 }
180 struct Deleter {
181 void (*destroy)(void*) noexcept;
182 void operator()(void* p) const noexcept { destroy(p); }
183 };
184 using Block = std::unique_ptr<void, Deleter>;
185
186 [[nodiscard]] Block clone(const void* src) const {
187 return Block(ops_.clone(src), Deleter{ops_.destroy});
188 }
189
190 std::optional<std::string> build_locked(const std::vector<ParamValue>& values,
191 InstrumentId inst,
192 ParamUpdateMsg& out,
193 std::vector<std::pair<std::size_t, Block>>* next) const {
194 const ParamSchema& schema = *schema_;
195 if (values.size() > ParamUpdateMsg::kMaxFields) {
196 return "at most " + std::to_string(ParamUpdateMsg::kMaxFields) +
197 " parameters per update, got " + std::to_string(values.size());
198 }
199 std::size_t first = 0;
200 std::size_t last = blocks_.size();
201 if (inst.valid()) {
202 if (!per_instrument_) {
203 return std::string(
204 "the strategy keeps one parameter set for all instruments; publish "
205 "without an instrument");
206 }
207 if (inst.value >= blocks_.size())
208 return "instrument " + std::to_string(inst.value) + " is not in the instrument table";
209 first = inst.value;
210 last = first + 1;
211 }
212 out = ParamUpdateMsg{};
214 out.hdr.flags = EventHeader::kSynthetic;
215 out.count = static_cast<std::uint32_t>(values.size());
216 for (std::size_t k = first; k < last; ++k) {
217 Block copy = clone(blocks_[k].get());
218 for (std::size_t i = 0; i < values.size(); ++i) {
219 const auto& [name, value] = values[i];
220 const ParamDesc* d = schema.find(name);
221 if (d == nullptr) return "unknown parameter '" + name + "'";
222 for (std::size_t j = 0; j < i; ++j) {
223 if (values[j].first == name) return "parameter '" + name + "' given twice";
224 }
225 if (auto err = d->parse(copy.get(), value)) return "parameter '" + name + "': " + *err;
226 out.field[i] = static_cast<std::uint16_t>(d - schema.begin());
227 out.value[i] = d->get_raw(copy.get());
228 }
229 if (auto err = ops_.validate(copy.get())) {
230 if (per_instrument_) return "instrument " + std::to_string(k) + ": " + *err;
231 return err;
232 }
233 if (next != nullptr) next->emplace_back(k, std::move(copy));
234 }
235 return std::nullopt;
236 }
237
238 ParamSink sink_;
239 const ParamSchema* schema_;
240 BlockOps ops_;
241 bool per_instrument_;
242 std::vector<Block> blocks_; // one per instrument, or the shared set
243 mutable std::mutex mutex_;
244 std::uint64_t seq_ = 0;
245 std::uint64_t refused_ = 0;
246 bool closed_ = false;
247};
248
249} // namespace fastmm
Definition param_publisher.hpp:57
bool closed() const
Definition param_publisher.hpp:134
std::string describe(InstrumentId inst=kAllInstruments) const
name=value pairs of the copy: one instrument's, or the shared set.
Definition param_publisher.hpp:166
bool publish(const std::vector< ParamValue > &values, InstrumentId inst=kAllInstruments)
Validates and sends one update.
Definition param_publisher.hpp:93
std::optional< std::string > build(const std::vector< ParamValue > &values, InstrumentId inst, ParamUpdateMsg &out) const
The update publish() would send, without sending it or changing the copy; the error message when it i...
Definition param_publisher.hpp:122
ParamPublisher(const ParamPublisher &)=delete
void close()
Later publishes return false (the session has stopped).
Definition param_publisher.hpp:130
std::uint64_t publish_as(const std::vector< ParamValue > &values, InstrumentId inst, ParamUpdateMsg::Origin origin, std::string_view source)
publish() with the update's origin and source (ParamUpdateMsg::source, cut to 39 bytes).
Definition param_publisher.hpp:98
ParamPublisher & operator=(const ParamPublisher &)=delete
std::vector< Value > values(InstrumentId inst=kAllInstruments) const
Definition param_publisher.hpp:155
const ParamSchema & schema() const
Definition param_publisher.hpp:147
std::uint64_t refused() const
Definition param_publisher.hpp:143
static constexpr InstrumentId kAllInstruments
Definition param_publisher.hpp:60
std::uint64_t published() const
Updates sent, and updates the sink refused.
Definition param_publisher.hpp:139
std::pair< std::string, std::string > ParamValue
name, value as ParamDesc::parse takes
Definition param_publisher.hpp:59
Definition params.hpp:102
const ParamDesc * begin() const noexcept
Definition params.hpp:112
std::size_t size() const noexcept
Definition params.hpp:111
const ParamDesc * find(std::string_view name) const noexcept
Definition params.hpp:105
Event bus message structs (5.5).
Definition strategy.hpp:16
@ ParamUpdate
new strategy parameter values (ParamUpdateMsg)
Timestamp wall_now() noexcept
CLOCK_REALTIME via vDSO (~20 ns). Implemented in src/core/time.cpp.
constexpr void init_header(M &m, EventType type, InstrumentId inst={}, VenueId venue={}, std::uint32_t len=static_cast< std::uint32_t >(sizeof(M))) noexcept
For fixed-size messages len defaults to sizeof(M); BookDeltaMsg passes size_for().
Definition messages.hpp:75
StrongId< struct InstrumentTag, std::uint32_t > InstrumentId
Dense index into the engine's instrument table.
Definition strong_id.hpp:38
ParamType
Definition params.hpp:55
Strategy parameter schema (8.6, ADR-0012).
Strongly typed identifiers.
@ kSynthetic
generated locally (reconcile fill, sim), not from the venue
Definition messages.hpp:39
Definition params.hpp:81
std::int64_t(* get_raw)(const void *obj) noexcept
The field as the int64 a ParamUpdateMsg carries, and back (engine thread: no checks).
Definition params.hpp:96
Copying and freeing a parameter block whose layout is known only at run time (strategies/hot_params....
Definition param_publisher.hpp:64
std::optional< std::string >(* validate)(const void *p)
Definition param_publisher.hpp:67
void(* destroy)(void *p) noexcept
Definition param_publisher.hpp:66
void *(* clone)(const void *src)
Definition param_publisher.hpp:65
Every parameter of the copy, in schema order: one instrument's, or the shared set.
Definition param_publisher.hpp:149
ParamType type
Definition param_publisher.hpp:151
std::string name
Definition param_publisher.hpp:150
std::string text
ParamDesc::format.
Definition param_publisher.hpp:153
std::int64_t raw
ParamDesc::get_raw.
Definition param_publisher.hpp:152
Where a built update goes; push returns false when it was not accepted (full).
Definition param_publisher.hpp:45
static ParamSink to_ring(MsgRing &ring) noexcept
The SPSC ring an engine's RingFeed polls.
Definition param_publisher.hpp:50
bool(* push)(void *ctx, const ParamUpdateMsg &m) noexcept
Definition param_publisher.hpp:47
void * ctx
Definition param_publisher.hpp:46
New strategy parameter values (ADR-0013): up to kMaxFields (field index, raw value) pairs,...
Definition messages.hpp:574
Origin
Definition messages.hpp:579
@ Strategy
the strategy's own publisher (a slow tier, a model), or not recorded
static constexpr std::size_t kMaxFields
Definition messages.hpp:575
static constexpr InstrumentId kAllInstruments
hdr.instrument of an update for every instrument: the invalid id.
Definition messages.hpp:578
Time primitives.