Eclipse SUMO - Simulation of Urban MObility
Loading...
Searching...
No Matches
CCHMetricFamily.h
Go to the documentation of this file.
1/****************************************************************************/
2// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3// Copyright (C) 2001-2026 German Aerospace Center (DLR) and others.
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// https://www.eclipse.org/legal/epl-2.0/
7// This Source Code may also be made available under the following Secondary
8// Licenses when the conditions for such availability set forth in the Eclipse
9// Public License 2.0 are satisfied: GNU General Public License, version 2
10// or later which is available at
11// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13/****************************************************************************/
18// A family of per-vehicle-type CCH metrics over one shared CCHGraph -- the
19// MetricProvider machinery behind CCHRouter, shared between the simulation
20// and duarouter the same way CCHGraph shares the topology.
21//
22// Keying by the vehicle TYPE (rather than the class) makes everything the
23// effort function reads from the vehicle exact per metric: the type's
24// maximum speed, per-class edge speed restrictions, routing preferences,
25// the bicycle speed table and one frozen weights.random-factor realization;
26// only individual speed-factor draws within one type share a metric (the
27// same approximation the CH family makes with its per-class prototype
28// vehicles, but at finer granularity).
29//
30// Two modes:
31//
32// STATIC -- weights never change while a (type, weight period) pair is in
33// use. Metrics are customized lazily on the first query of a pair under a
34// mutex and cached (duarouter, marouter and the simulation's free-flow
35// routers). When the efforts do change between queries -- a runtime
36// permission flip, marouter's travel times after an assignment iteration
37// -- the host flags it (flagStale) and every cached metric is refilled
38// and re-customized on the next query.
39//
40// LIVE -- weights track an adaptive speed table (the rerouting device).
41// Metrics are double-buffered: worker threads acquire-load the published
42// front while the owner refills and customizes the back at its barrier
43// and flips, so queries never observe a metric mid-customization. The
44// customize is sparse (RoutingKit partial customization over the edges
45// whose effort actually moved, behind a configurable deadband) with
46// wholesale refills after permission flips. Staleness is bounded to one
47// barrier -- CH's weightPeriod semantics: once any metric exists,
48// customize(now) must run at every barrier where an effort moved (see
49// atBarrier), and an epoch pair guards the bound as an invariant.
50//
51// The family is intentionally ignorant of WHEN to customize -- only the
52// owner knows when no query is in flight (the simulation's adaptation
53// barrier, duarouter's mutex) -- and of HOW reference vehicles are built
54// (injected factory; V construction is host-specific).
55/****************************************************************************/
56#pragma once
57#include <config.h>
58
59#include <atomic>
60#include <cmath>
61#include <limits>
62#include <map>
63#include <memory>
64#include <mutex>
65#include <utility>
66#include <vector>
70
71
72// ===========================================================================
73// class definitions
74// ===========================================================================
84template<class E, class V, class K>
86public:
98 typedef V* (*RefVehicleFactory)(const K*, int);
101 typedef void (*WeightPatch)(const Graph*, const V*, std::vector<unsigned>&);
102
113 SUMOTime begin, SUMOTime weightPeriod,
114 RefVehicleFactory factory, WeightPatch patch) :
115 myGraph(graph), myFillEffort(fillEffort), myGateEffort(nullptr),
116 myFactory(factory), myPatch(patch), myLive(false),
117 myBegin(begin), myWeightPeriod(weightPeriod),
119 }
120
153 EffortOperation gateEffort, double updateFactor,
154 double updateConstant, RefVehicleFactory factory,
155 int ensembleK = 1) :
156 myGraph(graph), myFillEffort(fillEffort), myGateEffort(gateEffort),
157 myFactory(factory), myPatch(nullptr), myLive(true),
159 myUpdateFactor(updateFactor), myUpdateConstant(updateConstant),
160 myEnsembleK(ensembleK > 1 ? ensembleK : 1) {
161 const unsigned space = myGraph->edgeIdSpace();
162 for (int i = 0; i < 2; i++) {
163 // NaN = "never applied": the first pending occurrence always
164 // passes the deadband and primes the entry
165 myAppliedEffort[i].assign(space, std::numeric_limits<double>::quiet_NaN());
166 myPendingFlag[i].assign(space, 0);
167 }
168 }
169
171 for (LiveMetric* c : myLiveMetrics) {
172 delete c->refVehicle;
173 delete c;
174 }
175 for (auto& item : myStaticMetrics) {
176 delete item.second.refVehicle;
177 }
178 }
179
182
192 MetricPtr get(const K* key, SUMOVehicleClass vClass, SUMOTime time, const V* veh) {
193 std::lock_guard<std::mutex> lock(myStaticLock);
194 if (myStale) {
195 for (auto& item : myStaticMetrics) {
196 StaticMetric& sm = item.second;
197 fillStatic(sm, item.first.second, time, sm.refVehicle != nullptr ? sm.refVehicle : veh, veh);
198 sm.metric->customize();
199 }
200 myStale = false;
201 }
202 const int period = periodOf(time);
203 const std::pair<const K*, int> mapKey(key, period);
204 auto it = myStaticMetrics.find(mapKey);
205 if (it == myStaticMetrics.end()) {
206 StaticMetric& sm = myStaticMetrics[mapKey];
207 sm.vClass = vClass;
208 const V* ref = veh;
209 if (myFactory != nullptr) {
210 sm.refVehicle = myFactory(key, 0);
211 ref = sm.refVehicle;
212 }
213 fillStatic(sm, period, time, ref, veh);
215 myGraph->cch(), sm.weights));
216 sm.metric->customize();
217 return sm.metric.get();
218 }
219 return it->second.metric.get();
220 }
221
226 return SUMOTime_MAX;
227 }
228 return myBegin + (periodOf(time) + 1) * myWeightPeriod;
229 }
230
236 void flagStale() {
237 std::lock_guard<std::mutex> lock(myStaticLock);
238 myStale = true;
239 }
240
243 flagStale();
244 }
246
249
253 void seedKey(const K* key) {
254 if (myByKey.count(key) == 0) {
255 std::vector<LiveMetric*>& slots = myByKey[key];
256 for (int k = 0; k < myEnsembleK; k++) {
257 slots.push_back(buildLiveMetric(key, k));
258 }
259 }
260 }
261
264 void markDirty(const E* e) {
265 const int id = e->getNumericalID();
266 if (id < 0 || id >= (int)myPendingFlag[0].size()) {
267 return;
268 }
269 for (int i = 0; i < 2; i++) {
270 if (!myPendingFlag[i][id]) {
271 myPendingFlag[i][id] = 1;
272 myPendingList[i].push_back(e);
273 }
274 }
275 }
276
282 void invalidateEdge(const E* e) {
283 const int id = e->getNumericalID();
284 if (id < 0 || id >= (int)myPendingFlag[0].size()) {
285 return;
286 }
287 markDirty(e);
288 // NaN sentinel: the deadband always passes, so the flip reaches the
289 // metric at the next barrier even though the speed table did not move
290 myAppliedEffort[0][id] = std::numeric_limits<double>::quiet_NaN();
291 myAppliedEffort[1][id] = std::numeric_limits<double>::quiet_NaN();
293 myMetricStale.store(true, std::memory_order_release);
294 }
295
305 void atBarrier(double now, bool effortsMoved) {
306 if (effortsMoved) {
307 mySpeedEpoch.fetch_add(1, std::memory_order_release);
308 }
309 if (myQueried.exchange(false, std::memory_order_acq_rel)
310 || myMetricStale.load(std::memory_order_acquire)
311 || (effortsMoved && !myLiveMetrics.empty())) {
312 customize(now);
313 }
314 }
315
320 void customize(double now) {
321 // types that queried since the last barrier without a metric
322 // (streamed route files, TraCI-added types): create their state now
323 {
324 std::lock_guard<std::mutex> lock(myWantedLock);
325 for (const K* key : myWantedKeys) {
326 seedKey(key);
327 }
328 myWantedKeys.clear();
329 }
330 if (myLiveMetrics.empty()) {
331 return;
332 }
333 // buffers flip in lockstep across types (every call processes all)
334 const int backShared = 1 - myLiveMetrics.front()->frontIndex;
335 // Permission flip pending: run a FULL refill on this pass's back
336 // buffer (the other buffer gets its full refill on the next pass --
337 // myFullFillsPending counts both down). The fills below re-prime the
338 // graph's connection masks, which the flip's caller invalidated.
339 bool forceFull = false;
340 if (myFullFillsPending > 0) {
342 forceFull = true;
343 }
344 // Deadband pass, once, on the type-shared gate effort: accept edges
345 // whose effort moved by more than BOTH bounds since this buffer last
346 // applied them; rejected edges stay pending (see the constructor doc)
347 std::vector<const E*> accepted;
348 if (!forceFull && myLiveMetrics.front()->metric[backShared] != nullptr) {
349 std::vector<const E*> stillPending;
350 for (const E* e : myPendingList[backShared]) {
351 const int id = e->getNumericalID();
352 const double effNow = myGateEffort(e, nullptr, now);
353 const double effApplied = myAppliedEffort[backShared][id];
354 bool pass = true;
355 if (!std::isnan(effApplied) && effApplied > 0. && effNow > 0.) {
356 const double hi = MAX2(effNow, effApplied);
357 const double lo = MIN2(effNow, effApplied);
358 pass = (hi / lo > myUpdateFactor) && (hi - lo > myUpdateConstant);
359 }
360 if (pass) {
361 accepted.push_back(e);
362 myAppliedEffort[backShared][id] = effNow;
363 myPendingFlag[backShared][id] = 0;
364 } else {
365 stillPending.push_back(e);
366 }
367 }
368 myPendingList[backShared].swap(stillPending);
369 }
370 for (LiveMetric* c : myLiveMetrics) {
371 const int back = 1 - c->frontIndex;
372 // Fill from the live efforts, masking arcs the class is not
373 // permitted on -- this is where per-class permissions AND active
374 // closures become inf_weight. The fill reference is the type's
375 // OWNED refVehicle, so the effort captures the type's extras
376 // exactly (including each folded via edge inside viaChainEffort)
377 // and freezes ONE random realization per metric -- the same
378 // approximation CHRouterWrapper makes per hierarchy; exact
379 // per-vehicle randomization remains the domain of the exact
380 // routers.
381 if (c->metric[back] == nullptr) {
382 // first use of this buffer for THIS type only (streamed/late
383 // vType): no previous state to diff against for this type,
384 // but every other already-running type's sparse deadband
385 // state is unaffected, so this branch must NOT trigger the
386 // shared wipe below (which would reset the type-shared
387 // myPendingList/myAppliedEffort for everybody just because
388 // one type is new)
389 myGraph->fillInputWeights(myFillEffort, c->vClass, c->refVehicle, now, c->weight[back]);
390 c->metric[back] = std::make_shared<RoutingKit::CustomizableContractionHierarchyMetric>(
391 myGraph->cch(), c->weight[back]);
392 c->metric[back]->customize(); // serial; avoids OpenMP oversubscription under FOX
393 } else if (forceFull) {
394 // permission flip: wholesale refill in place from the
395 // re-primed masks and live permissions (the metric already
396 // references weight[back]), then a full customize. This is
397 // the ONLY case that invalidates the type-shared deadband
398 // state below, since it is the only one that actually
399 // changes what "applied" means for every type at once.
400 myGraph->fillInputWeights(myFillEffort, c->vClass, c->refVehicle, now, c->weight[back]);
401 c->metric[back]->customize();
402 } else {
403 // SPARSE PATH: metric[back] is the customization of the
404 // current contents of weight[back]. Recompute only the arcs
405 // of accepted edges (edge->arc reverse image), write the
406 // ones that moved, and propagate through affected triangles
407 // only. Cost scales with traffic transitions, not with the
408 // network.
409 std::vector<unsigned>& applied = c->weight[back];
410 c->partial->reset(myGraph->cch());
411 unsigned changed = 0;
412 for (const E* e : accepted) {
413 for (const unsigned a : myGraph->arcsOfEdge(e)) {
414 const unsigned newW = myGraph->computeArcWeight(a, myFillEffort, c->vClass, c->refVehicle, now);
415 if (newW != applied[a]) {
416 applied[a] = newW;
417 c->partial->update_arc(a); // takes INPUT arc ids
418 ++changed;
419 }
420 }
421 }
422 if (changed > 0) {
423 c->partial->customize(*c->metric[back]);
424 }
425 }
426 // publish lock-free: release store at the barrier, worker
427 // acquire-loads on the hot path
428 c->frontIndex = back;
429 c->front.store(c->metric[back].get(), std::memory_order_release);
430 }
431 if (forceFull) {
432 // a permission flip's full refill matched every arc to the live
433 // efforts/masks for this buffer: type-shared deadband entries
434 // are stale; NaN re-arms the first-change auto-pass. (A plain
435 // per-type first-fill above does NOT reach here -- it must not
436 // wipe deadband state that other, already-running types still
437 // depend on.)
438 for (const E* e : myPendingList[backShared]) {
439 myPendingFlag[backShared][e->getNumericalID()] = 0;
440 }
441 myPendingList[backShared].clear();
442 myAppliedEffort[backShared].assign(myAppliedEffort[backShared].size(),
443 std::numeric_limits<double>::quiet_NaN());
444 }
445 // every published front now reflects the live permissions (the other
446 // buffer, if still pending a full refill, is repaired before its
447 // next publish) and the live efforts as of this barrier (both epoch
448 // counters only move on the owner's thread at the barrier, so the
449 // pair cannot tear)
450 myMetricStale.store(false, std::memory_order_release);
451 myPublishedEpoch.store(mySpeedEpoch.load(std::memory_order_relaxed),
452 std::memory_order_release);
453 }
454
459 MetricPtr published(const K* key, const std::string& vehID) {
460 const auto it = myByKey.find(key);
461 if (it == myByKey.end()) {
462 // streamed-in type without a metric yet: register it for
463 // creation at the next barrier and fall back
464 {
465 std::lock_guard<std::mutex> lock(myWantedLock);
466 myWantedKeys.push_back(key);
467 }
468 myQueried.store(true, std::memory_order_relaxed);
469 return nullptr;
470 }
471 // mark the metric as consumed. Test before set: an unconditional
472 // store from every query would ping-pong the cache line between
473 // cores; the read is shared and the store fires once per barrier
474 // window (relaxed: only gates bootstrap/re-arm, never visibility)
475 if (!myQueried.load(std::memory_order_relaxed)) {
476 myQueried.store(true, std::memory_order_relaxed);
477 }
478 // A permission flip that is not yet customized in makes every
479 // published metric unusable: it may still route THROUGH a
480 // just-closed edge, and CCHRouter's coverage test consults the LIVE
481 // permissions, which would wrongly bless the query. Route exactly
482 // until the barrier repairs the metrics.
483 if (myMetricStale.load(std::memory_order_acquire)) {
484 return nullptr;
485 }
486 // Safety invariant: never serve a metric the efforts have moved away
487 // from -- the exact routers see live weights and CH is bounded to
488 // one weightPeriod. atBarrier re-customizes at every moved barrier
489 // once metrics exist, so this cannot fire in steady state; if a gap
490 // ever opens, this burst routes on the exact fallback (live weights)
491 // and the queried flag set above arms the customize that closes it.
492 if (myPublishedEpoch.load(std::memory_order_acquire)
493 != mySpeedEpoch.load(std::memory_order_acquire)) {
494 return nullptr;
495 }
496 // stable slot assignment: FNV-1a over the id (std::hash is
497 // implementation-defined and would break cross-platform test
498 // reproducibility)
499 const LiveMetric* c = myEnsembleK == 1 ? it->second.front()
500 : it->second[fnv1a(vehID) % myEnsembleK];
501 return c->front.load(std::memory_order_acquire);
502 }
503
505 bool empty() const {
506 return myLiveMetrics.empty();
507 }
509
510private:
513 struct LiveMetric {
514 std::vector<unsigned> weight[2]; // ping-pong input-weight buffers, live whole run
515 std::shared_ptr<RoutingKit::CustomizableContractionHierarchyMetric> metric[2];
516 std::atomic<const RoutingKit::CustomizableContractionHierarchyMetric*> front{nullptr};
517 int frontIndex = 1; // first customize uses back = 0
520 V* refVehicle = nullptr;
523 std::shared_ptr<RoutingKit::CustomizableContractionHierarchyPartialCustomization> partial;
524 };
525
530 std::vector<unsigned> weights;
531 std::unique_ptr<RoutingKit::CustomizableContractionHierarchyMetric> metric;
535 V* refVehicle = nullptr;
537 bool filled = false;
540 std::vector<std::pair<unsigned, unsigned> > patched;
541 };
542
545 int periodOf(SUMOTime time) const {
546 if (myWeightPeriod > 0 && myWeightPeriod != SUMOTime_MAX && time > myBegin) {
547 return (int)((time - myBegin) / myWeightPeriod);
548 }
549 return 0;
550 }
551
561 void fillStatic(StaticMetric& sm, int period, SUMOTime time, const V* ref, const V* veh) {
562 const double fillTime = myWeightPeriod == SUMOTime_MAX
563 ? STEPS2TIME(time) : STEPS2TIME(myBegin + period * myWeightPeriod);
564 myGraph->fillInputWeights(myFillEffort, sm.vClass, ref, fillTime, sm.weights);
565 if (!sm.filled) {
566 sm.filled = true;
567 if (myPatch != nullptr) {
568 const std::vector<unsigned> unpatched(sm.weights);
569 myPatch(myGraph, veh, sm.weights);
570 for (unsigned a = 0; a < (unsigned)sm.weights.size(); a++) {
571 if (sm.weights[a] != unpatched[a]) {
572 sm.patched.emplace_back(a, sm.weights[a]);
573 }
574 }
575 }
576 } else {
577 for (const auto& p : sm.patched) {
578 sm.weights[p.first] = p.second;
579 }
580 }
581 }
582
584 static uint64_t fnv1a(const std::string& s) {
585 uint64_t h = 1469598103934665603ull;
586 for (const char ch : s) {
587 h = (h ^ (unsigned char)ch) * 1099511628211ull;
588 }
589 return h;
590 }
591
594 LiveMetric* buildLiveMetric(const K* key, int slot) {
595 LiveMetric* c = new LiveMetric();
596 c->refVehicle = myFactory(key, slot);
597 c->vClass = c->refVehicle->getVClass();
598 c->weight[0].resize(myGraph->arcCount());
599 c->weight[1].resize(myGraph->arcCount());
600 c->partial = std::make_shared<RoutingKit::CustomizableContractionHierarchyPartialCustomization>(myGraph->cch());
601 myLiveMetrics.push_back(c);
602 return c;
603 }
604
616 const bool myLive;
617
622 std::map<std::pair<const K*, int>, StaticMetric> myStaticMetrics;
623 std::mutex myStaticLock;
626 bool myStale = false;
628
635 int myEnsembleK = 1;
638 std::vector<LiveMetric*> myLiveMetrics;
639 std::map<const K*, std::vector<LiveMetric*> > myByKey;
643 std::vector<const K*> myWantedKeys;
644 std::mutex myWantedLock;
647 std::atomic<bool> myQueried{false};
650 std::atomic<bool> myMetricStale{false};
656 std::atomic<uint64_t> mySpeedEpoch{0};
657 std::atomic<uint64_t> myPublishedEpoch{0};
666 std::vector<double> myAppliedEffort[2];
667 std::vector<char> myPendingFlag[2];
668 std::vector<const E*> myPendingList[2];
670
671private:
674};
long long int SUMOTime
Definition GUI.h:36
#define STEPS2TIME(x)
Definition SUMOTime.h:58
#define SUMOTime_MAX
Definition SUMOTime.h:34
SUMOVehicleClass
Definition of vehicle classes to differ between different lane usage and authority types.
@ SVC_PASSENGER
vehicle is a passenger car (a "normal" car)
T MIN2(T a, T b)
Definition StdDefs.h:80
T MAX2(T a, T b)
Definition StdDefs.h:86
Metric-independent RoutingKit CCH topology over the PURE road graph.
Definition CCHGraph.h:86
unsigned edgeIdSpace() const
size of the edge numerical-id space the graph was built over (for callers keeping per-edge-id side ar...
Definition CCHGraph.h:237
unsigned arcCount() const
number of input arcs (== number of mapped connections)
Definition CCHGraph.h:231
double(* EffortOperation)(const E *const, const V *const, double)
effort callback signature, matching SUMOAbstractRouter::Operation
Definition CCHGraph.h:89
void fillInputWeights(EffortOperation effort, SUMOVehicleClass maskClass, const V *veh, double time, std::vector< unsigned > &weight) const
Fill a centisecond input-weight buffer for one vehicle class.
Definition CCHGraph.h:339
const RoutingKit::CustomizableContractionHierarchy & cch() const
the immutable CCH (share const& across clones)
Definition CCHGraph.h:226
const std::vector< unsigned > & arcsOfEdge(const E *e) const
the arcs whose weight depends on the given edge (arcs it heads plus arcs whose folded via chain conta...
Definition CCHGraph.h:273
unsigned computeArcWeight(unsigned a, EffortOperation effort, SUMOVehicleClass maskClass, const V *veh, double time) const
Recompute the input weight of one arc – the exact per-arc body of fillInputWeights (same masking,...
Definition CCHGraph.h:318
per-vehicle-type CCH metric store over a shared CCHGraph
std::map< const K *, std::vector< LiveMetric * > > myByKey
bool myStale
the efforts changed behind the cached metrics: refill and re-customize them all on the next get() (se...
std::vector< const E * > myPendingList[2]
MetricPtr get(const K *key, SUMOVehicleClass vClass, SUMOTime time, const V *veh)
the metric for (type key, period of time), built on the first query of the pair; nullptr before init....
void seedKey(const K *key)
allocate the metric state (all ensemble slots) for one type; call only while no query is in flight (s...
const Graph * myGraph
the immutable shared topology
EffortOperation myFillEffort
the effort the metrics are filled from
std::map< std::pair< const K *, int >, StaticMetric > myStaticMetrics
int periodOf(SUMOTime time) const
the weight period containing time: 0 is everything before begin and the whole run when the weights ar...
WeightPatch myPatch
post-fill weight hook (STATIC)
std::mutex myWantedLock
const bool myLive
LIVE or STATIC (fixed at construction)
std::vector< LiveMetric * > myLiveMetrics
every type's state, in creation order (barrier iteration) and by key (query lookup; only mutated whil...
void atBarrier(double now, bool effortsMoved)
the owner's customization barrier: bump the speed epoch when an effort moved and re-customize when it...
int myFullFillsPending
how many customize passes must run a FULL refill after a permission flip: 2 = both ping-pong buffers ...
std::atomic< bool > myQueried
whether a query arrived since the last barrier (bootstraps type creation and re-arms after resets – s...
RefVehicleFactory myFactory
reference-vehicle factory (required LIVE, optional STATIC)
std::atomic< uint64_t > myPublishedEpoch
void customize(double now)
refill + customize + publish every type's metric from the live efforts and permissions....
std::vector< const K * > myWantedKeys
types that queried but have no metric yet (demand streams, so types can appear after seeding); querie...
V *(* RefVehicleFactory)(const K *, int)
builds an OWNED effort-reference vehicle for a type: never registered, counted or inserted – it exist...
CCHMetricFamily(const Graph *graph, EffortOperation fillEffort, SUMOTime begin, SUMOTime weightPeriod, RefVehicleFactory factory, WeightPatch patch)
construct a STATIC family (lazy build per (type, period))
void fillStatic(StaticMetric &sm, int period, SUMOTime time, const V *ref, const V *veh)
(re)fill a STATIC metric's input weights from the live efforts. A weight-period grid evaluates at the...
CCHMetricFamily & operator=(const CCHMetricFamily &)=delete
bool empty() const
whether any type has live metric state yet
void flagStale()
the efforts behind the cached metrics changed – a runtime permission change in the simulation (the ca...
const RoutingKit::CustomizableContractionHierarchyMetric * MetricPtr
void invalidateEdge(const E *e)
a runtime permission change (closure / re-opening) hit this edge: queue it bypassing the deadband and...
std::atomic< uint64_t > mySpeedEpoch
barrier counter of the tracked efforts and the counter value the published metrics were customized fr...
CCHMetricFamily(const Graph *graph, EffortOperation fillEffort, EffortOperation gateEffort, double updateFactor, double updateConstant, RefVehicleFactory factory, int ensembleK=1)
construct a LIVE family (double-buffered, barrier-customized)
std::atomic< bool > myMetricStale
a permission flip has not yet been customized into the published metrics (queries divert to the exact...
CCHGraph< E, V > Graph
SUMOTime periodEnd(SUMOTime time) const
the end of the weight period containing the given time (SUMOTime_MAX when the weights are static); CC...
int myEnsembleK
frozen random-factor realizations per type (see the LIVE ctor)
EffortOperation myGateEffort
the type-independent effort of the LIVE deadband
Graph::EffortOperation EffortOperation
MetricPtr published(const K *key, const std::string &vehID)
the published metric for a type key and querying vehicle id (the id picks the ensemble slot – see the...
void flagPermissionsStale()
flagStale() under the simulation's name for the permission case
std::vector< double > myAppliedEffort[2]
per-buffer edge state for the sparse path: the gate effort each edge's arcs were last filled from (Na...
static uint64_t fnv1a(const std::string &s)
stable 64-bit FNV-1a for the ensemble slot assignment
void markDirty(const E *e)
queue an edge whose gate effort changed (both buffers; the deadband applies later,...
void(* WeightPatch)(const Graph *, const V *, std::vector< unsigned > &)
post-fill hook on the input weights (STATIC mode), e.g. duarouter masking the edges that restrict a t...
LiveMetric * buildLiveMetric(const K *key, int slot)
allocate LIVE metric state for one (type, ensemble slot) (owner's thread only)
std::vector< char > myPendingFlag[2]
std::mutex myStaticLock
CCHMetricFamily(const CCHMetricFamily &)=delete
const Graph * graph
per-type LIVE metric state. Heap-owned: the atomic makes it non-movable, so it cannot live in a map b...
V * refVehicle
OWNED effort-reference vehicle (see RefVehicleFactory)
std::vector< unsigned > weight[2]
std::atomic< const RoutingKit::CustomizableContractionHierarchyMetric * > front
std::shared_ptr< RoutingKit::CustomizableContractionHierarchyPartialCustomization > partial
partial-customization worker (queue over the shared CCH); one per metric – its queue is drained by ev...
std::shared_ptr< RoutingKit::CustomizableContractionHierarchyMetric > metric[2]
per-(type, period) STATIC metric state. RoutingKit metrics BORROW their input-weight buffer,...
std::vector< unsigned > weights
V * refVehicle
OWNED reference vehicle (nullptr when filling with the querying vehicle)
std::vector< std::pair< unsigned, unsigned > > patched
the (arc, weight) pairs the patch wrote on the first fill, re-applied on every refill
bool filled
whether the first fill (which runs the patch) is done
std::unique_ptr< RoutingKit::CustomizableContractionHierarchyMetric > metric