fixed bug in attaching nics to hosts, changed documentation format and generator (buggy but usable)

This commit is contained in:
2025-09-14 00:40:53 -04:00
parent 9ab64e18a4
commit c4141cd683
40 changed files with 5223 additions and 577 deletions

View File

@@ -1,9 +1,15 @@
# core/error.h # core/error.h
## `dofs::log_error`
## Free functions `void log_error(std::string_view type, std::string_view message, std::optional<uint64_t> timestamp = std::nullopt) noexcept;`
## `DOFS_ERROR`
### `std::mutex &error_mutex() noexcept;` `#define DOFS_ERROR(TYPE, MSG)`
### `inline T&& show(std::string_view name, T&& value) noexcept { ... }` ## `DOFS_ERROR_ST`
### `std::lock_guard<std::mutex> lock(error_mutex());` `#define DOFS_ERROR_ST(TYPE, SRC, MSG, TS)`
### `inline T&& eval_and_show(std::string_view expr, T&& value) noexcept { ... }` ## `DOFS_ERROR_T`
### `std::lock_guard<std::mutex> lock(error_mutex());` `#define DOFS_ERROR_T(TYPE, MSG, TS)`
## `DOFS_EVAL`
`#define DOFS_EVAL(EXPR)`
## `DOFS_SHOW`
`#define DOFS_SHOW(VAR)`
## `FORMAT`
`#define FORMAT(VAR)`

View File

@@ -1,10 +1,32 @@
# core/host.h # core/host.h
## `Host::Host`
`Host(Simulator *const sim, NodeId id) noexcept`
## class NetworkNic — public interface Binds a host to a simulator under the `NodeId` id.
### `Host(Simulator *const sim, NodeId id) noexcept;` ## `Host::Host`
### `virtual ~Host() = default;` `Host(const Host &) = delete`
### `NetworkNic *nic() const noexcept { ... }`
### `void attach_nic(NetworkNic* nic) noexcept;` Binds a host to a simulator under the `NodeId` id.
### `void detach_nic(NetworkNic* nic) noexcept;`
### `Host(const Host &) = delete;` ## `Host::attach_nic`
`void attach_nic(NetworkNic *nic) noexcept`
Attaches a `NetworkNic` to the host.
## `Host::detach_nic`
`void detach_nic(NetworkNic *nic) noexcept`
Detaches a `NetworkNic` to the host.
## `Host::nic`
`NetworkNic *nic() const noexcept`
## `Host::operator=`
`Host &operator=(const Host &) = delete`
## `Host::recv_flow`
`virtual void recv_flow(NodeId src, FlowId flow, FlowPriority priority, Bytes flow_size) = 0`
## `Host::recv_frame`
`virtual void recv_frame(const Packet &frame) = 0`
## `Host::recv_mgmt_msg`
`virtual void recv_mgmt_msg(std::unique_ptr<struct MgmtMsg> msg) = 0`
## `Host::~Host`
`virtual ~Host() = default`

View File

@@ -1,13 +1,31 @@
# core/logger.h # core/logger.h
## `Logger::Logger`
## class Logger — public interface `Logger(Logger &&) = delete`
## `Logger::Logger`
### `Logger(std::string_view path, bool append) noexcept;` `Logger(const Logger &) = delete`
### `~Logger() noexcept;` ## `Logger::Logger`
### `Logger(const Logger &) = delete;` `Logger(std::string_view path, bool append) noexcept`
### `Logger(Logger &&) = delete;` ## `Logger::close`
### `bool is_open() const noexcept { ... }` `void close() noexcept`
### `void write_line(std::string_view line) noexcept;` ## `Logger::flush`
### `void flush() noexcept;` `void flush() noexcept`
### `void close() noexcept;` ## `Logger::is_open`
### `std::string_view path() const noexcept { ... }` `bool is_open() const noexcept`
## `Logger::operator=`
`Logger &operator=(Logger &&) = delete`
## `Logger::operator=`
`Logger &operator=(const Logger &) = delete`
## `Logger::path`
`std::string_view path() const noexcept`
## `Logger::write_line`
`void write_line(std::string_view line) noexcept`
## `Logger::~Logger`
`~Logger() noexcept`
## `dofs::close`
`void close() noexcept;`
## `dofs::flush`
`void flush() noexcept;`
## `dofs::write_line`
`void write_line(std::string_view line) noexcept;`
## `open`
`private: bool open(std::ios::openmode mode) noexcept;`

View File

@@ -1,13 +1,21 @@
# core/node.h # core/node.h
## `Node::Node`
## class Node — public interface `Node(Simulator *const sim, NodeId id, NodeType type) noexcept`
## `Node::Node`
### `Node(Simulator *const sim, NodeId id, NodeType type) noexcept;` `Node(const Node &) = delete`
### `virtual ~Node() = default;` ## `Node::boot`
### `NodeId id() const noexcept;` `void boot(Time boottime_ns)`
### `NodeStatus status() const noexcept;` ## `Node::id`
### `NodeType type() const noexcept;` `NodeId id() const noexcept`
### `void set_status(NodeStatus s) noexcept;` ## `Node::operator=`
### `void boot(Time boottime_ns);` `Node &operator=(const Node &) = delete`
### `void reboot(Time boottime_ns);` ## `Node::reboot`
### `Node(const Node &) = delete;` `void reboot(Time boottime_ns)`
## `Node::set_status`
`void set_status(NodeStatus s) noexcept`
## `Node::status`
`NodeStatus status() const noexcept`
## `Node::type`
`NodeType type() const noexcept`
## `Node::~Node`
`virtual ~Node() = default`

View File

@@ -1,16 +1,45 @@
# core/rng.h # core/rng.h
## `Rng::Rng`
## class Rng — public interface `explicit Rng(seed_type seed = default_seed()) noexcept : _eng(seed)`
## `Rng::choose_weighted`
### `: _eng(seed) { ... }` `template<typename T> template<typename T> T choose_weighted(const std::vector<std::pair<double, T>> &items)`
### `void seed(seed_type s) noexcept { ... }` ## `Rng::choose_weighted`
### `_eng.seed(s);` `template<typename T> template<typename T> T choose_weighted(std::initializer_list<std::pair<double, T>> items)`
### `double uniform01() { ... }` ## `Rng::poisson`
### `Int uniform_range(Int lo_inclusive, Int hi_exclusive) { ... }` `std::uint64_t poisson(double lambda)`
### `Int uniform_range(Int hi_exclusive) { ... }` ## `Rng::seed`
### `double uniform_range(double lo_inclusive, double hi_exclusive) { ... }` `void seed(seed_type s) noexcept`
### `std::uint64_t poisson(double lambda) { ... }` ## `Rng::uniform01`
### `T choose_weighted(const std::vector<std::pair<double, T>>& items) { ... }` `double uniform01()`
### `return choose_weighted_impl(items.begin(), items.end());` ## `Rng::uniform_range`
### `T choose_weighted(std::initializer_list<std::pair<double, T>> items) { ... }` `double uniform_range(double lo_inclusive, double hi_exclusive)`
### `return choose_weighted_impl(items.begin(), items.end());` ## `Rng::uniform_range`
`template<typename Int,
typename = std::enable_if_t<std::is_integral<Int>::value>> template<typename Int, typename = std::enable_if_t<std::is_integral<Int>::value>> Int uniform_range(Int hi_exclusive)`
## `Rng::uniform_range`
`template<typename Int,
typename = std::enable_if_t<std::is_integral<Int>::value>> template<typename Int, typename = std::enable_if_t<std::is_integral<Int>::value>> Int uniform_range(Int lo_inclusive, Int hi_exclusive)`
## `choose_weighted`
`template <typename T> T choose_weighted(const std::vector<std::pair<double, T>> &items){`
## `choose_weighted`
`template <typename T> T choose_weighted(std::initializer_list<std::pair<double, T>> items){`
## `choose_weighted_impl`
`return choose_weighted_impl(items.begin(), items.end());`
## `choose_weighted_impl`
`return choose_weighted_impl(items.begin(), items.end());`
## `choose_weighted_impl`
`template <typename Iter> auto choose_weighted_impl(Iter first, Iter last) -> typename std::iterator_traits<Iter>::value_type::second_type{`
## `default_seed`
`static constexpr seed_type default_seed() noexcept{`
## `dofs::seed`
`void seed(seed_type s) noexcept{`
## `poisson`
`std::uint64_t poisson(double lambda){`
## `uniform01`
`double uniform01(){`
## `uniform_range`
`Int uniform_range(Int hi_exclusive){`
## `uniform_range`
`Int uniform_range(Int lo_inclusive, Int hi_exclusive){`
## `uniform_range`
`double uniform_range(double lo_inclusive, double hi_exclusive){`

View File

@@ -1,27 +1,49 @@
# core/simulator.h # core/simulator.h
## `Simulator::Cmp::operator`
## Free functions `bool operator()(const Item &a, const Item &b) const noexcept`
## `Simulator::Simulator`
### `std::numeric_limits<InstanceId>::max();` `Simulator() = default`
### `std::numeric_limits<LinkId>::max();` ## `Simulator::cancel`
`bool cancel(EventId id)`
## class Rng — public interface ## `Simulator::create_link`
`std::pair<LinkId, Link *> create_link(NetworkNode *a, PortId a_port, NetworkNode *b, PortId b_port, Time latency, double bandwidth_gbps)`
### `Simulator() = default;` ## `Simulator::create_rng`
### `static std::pair<InstanceId, Simulator *> create_simulator(InstanceId id);` `Rng *create_rng(std::uint64_t seed)`
### `static Simulator *get_simulator(InstanceId id) noexcept;` ## `Simulator::create_simulator`
### `Time now() const noexcept;` `static std::pair<InstanceId, Simulator *> create_simulator(InstanceId id)`
### `EventId schedule_at(Time abs_time, F&& f, Args&&... args) { ... }` ## `Simulator::flush_after`
### `_event_pq.push(std::move(it));` `void flush_after(Time grace) noexcept`
### `EventId schedule_after(Time delay, F&& f, Args&&... args) { ... }` ## `Simulator::get_link`
### `bool cancel(EventId id);` `Link *get_link(LinkId id) noexcept`
### `bool run_next();` ## `Simulator::get_link`
### `void run_until(Time end_time);` `Link const *get_link(LinkId id) const noexcept`
### `void lock() noexcept;` ## `Simulator::get_rng`
### `bool is_locked() const noexcept { ... }` `Rng *get_rng() noexcept`
### `void flush_after(Time grace) noexcept;` ## `Simulator::get_rng`
### `Rng* create_rng(std::uint64_t seed);` `Rng const *get_rng() const noexcept`
### `Rng* get_rng() noexcept;` ## `Simulator::get_simulator`
### `Rng const* get_rng() const noexcept;` `static Simulator *get_simulator(InstanceId id) noexcept`
### `Link* get_link(LinkId id) noexcept;` ## `Simulator::is_locked`
### `Link const* get_link(LinkId id) const noexcept;` `bool is_locked() const noexcept`
## `Simulator::lock`
`void lock() noexcept`
## `Simulator::now`
`Time now() const noexcept`
## `Simulator::run_next`
`bool run_next()`
## `Simulator::run_until`
`void run_until(Time end_time)`
## `Simulator::schedule_after`
`template<class F, class... Args> template<class F, class... Args> EventId schedule_after(Time delay, F&&f, Args&&... args)`
## `Simulator::schedule_at`
`template<class F, class... Args> template<class F, class... Args> EventId schedule_at(Time abs_time, F&&f, Args&&... args)`
## `cancel`
`bool cancel(EventId id);`
## `flush_after`
`void flush_after(Time grace) noexcept;`
## `lock`
`void lock() noexcept;`
## `run_next`
`bool run_next();`
## `run_until`
`void run_until(Time end_time);`

View File

@@ -1,36 +1,61 @@
# core/time.h # core/time.h
## `Time`
## Free functions `return Time(a._nsec * b._nsec);`
## `Time`
### `constexpr Time operator""_ns(unsigned long long v) noexcept { ... }` `return Time(a._nsec + b._nsec);`
### `return Time::from_ns(static_cast<Time::rep>(v));` ## `Time`
### `constexpr Time operator""_us(unsigned long long v) noexcept { ... }` `return Time(a._nsec - b._nsec);`
### `return Time::from_us(static_cast<Time::rep>(v));` ## `Time`
### `constexpr Time operator""_ms(unsigned long long v) noexcept { ... }` `return Time(ms * 1000ULL * 1000ULL);`
### `return Time::from_ms(static_cast<Time::rep>(v));` ## `Time`
### `constexpr Time operator""_s (unsigned long long v) noexcept { ... }` `return Time(ns);`
### `return Time::from_s (static_cast<Time::rep>(v));` ## `Time`
`return Time(s * 1000ULL * 1000ULL * 1000ULL);`
## class Time — public interface ## `Time`
`return Time(this->_nsec - t._nsec);`
### `constexpr Time() : _nsec(0) { ... }` ## `Time`
### `explicit constexpr Time(rep ns) : _nsec(ns) { ... }` `return Time(us * 1000ULL);`
### `static constexpr Time from_ns(rep ns) noexcept { ... }` ## `Time::Time`
### `return Time(ns);` `constexpr Time() : _nsec(0)`
### `static constexpr Time from_us(rep us) noexcept { ... }` ## `Time::Time`
### `return Time(us * 1000ULL);` `explicit constexpr Time(rep ns) : _nsec(ns)`
### `static constexpr Time from_ms(rep ms) noexcept { ... }` ## `Time::count`
### `return Time(ms * 1000ULL * 1000ULL);` `constexpr rep count() const noexcept`
### `static constexpr Time from_s (rep s ) noexcept { ... }` ## `Time::from_ms`
### `return Time(s * 1000ULL * 1000ULL * 1000ULL);` `static constexpr Time from_ms(rep ms) noexcept`
### `constexpr rep ns() const noexcept { ... }` ## `Time::from_ns`
### `constexpr rep count() const noexcept { ... }` `static constexpr Time from_ns(rep ns) noexcept`
### `static constexpr rep us_to_ns(rep us) noexcept { ... }` ## `Time::from_s`
### `static constexpr rep ms_to_ns(rep ms) noexcept { ... }` `static constexpr Time from_s (rep s ) noexcept`
### `return Time(a._nsec + b._nsec);` ## `Time::from_us`
### `return Time(a._nsec * b._nsec);` `static constexpr Time from_us(rep us) noexcept`
### `return safe_sub(a, b);` ## `Time::ms_to_ns`
### `if (a._nsec < b._nsec) { ... }` `static constexpr rep ms_to_ns(rep ms) noexcept`
### `return Time(a._nsec - b._nsec);` ## `Time::ns`
### `constexpr Time unsafe_sub(Time t) const noexcept { ... }` `constexpr rep ns() const noexcept`
### `return Time(this->_nsec - t._nsec);` ## `Time::us_to_ns`
`static constexpr rep us_to_ns(rep us) noexcept`
## `_ms`
`constexpr Time operator _ms(unsigned long long v) noexcept{`
## `_ns`
`constexpr Time operator _ns(unsigned long long v) noexcept{`
## `_s`
`constexpr Time operator _s (unsigned long long v) noexcept{`
## `_us`
`constexpr Time operator _us(unsigned long long v) noexcept{`
## `from_ms`
`static constexpr Time from_ms(rep ms) noexcept{`
## `from_ns`
`static constexpr Time from_ns(rep ns) noexcept{`
## `from_s`
`static constexpr Time from_s (rep s ) noexcept{`
## `from_us`
`static constexpr Time from_us(rep us) noexcept{`
## `ms_to_ns`
`static constexpr rep ms_to_ns(rep ms) noexcept{`
## `safe_sub`
`friend constexpr std::optional<Time> safe_sub(Time a, Time b) noexcept{`
## `safe_sub`
`return safe_sub(a, b);`
## `us_to_ns`
`static constexpr rep us_to_ns(rep us) noexcept{`

View File

@@ -1,13 +1,13 @@
# core/timer.h # core/timer.h
## `Timer::Timer`
## class Timer — public interface `Timer()`
## `Timer::elapsed`
### `Timer() { ... }` `Time elapsed() const noexcept`
### `init();` ## `Timer::init`
### `void init() noexcept { ... }` `void init() noexcept`
### `_start = clock::now();` ## `Timer::now`
### `Time start() const noexcept { ... }` `Time now() const noexcept`
### `auto tp = _start.time_since_epoch();` ## `Timer::start`
### `Time now() const noexcept { ... }` `Time start() const noexcept`
### `auto tp = clock::now().time_since_epoch();` ## `dofs::init`
### `Time elapsed() const noexcept { ... }` `void init() noexcept{`

View File

@@ -1,6 +1,3 @@
# core/types.h # core/types.h
## `dofs::ipv4`
## Free functions `inline IPv4Addr ipv4(NodeId n, PortId p) noexcept{`
### `inline IPv4Addr ipv4(NodeId n, PortId p) noexcept { ... }`
### `return (static_cast<uint32_t>(n) << 16) | static_cast<uint32_t>(p);`

View File

@@ -1,19 +1,15 @@
# hosts/mgmt_msg.h # hosts/mgmt_msg.h
## `EndSimulationMsg::kind`
## class MgmtMsg — public interface `MgmtKind kind() const noexcept override`
## `HeartbeatMsg::HeartbeatMsg`
### `virtual ~MgmtMsg() = default;` `explicit HeartbeatMsg(NodeId sid, NodeStatus st, Time t) noexcept : subscriber_id(sid), status(st), generated_at(t)`
## `HeartbeatMsg::kind`
## class HeartbeatMsg — public interface `MgmtKind kind() const noexcept override`
## `JobFinishedMsg::JobFinishedMsg`
### `: subscriber_id(sid), status(st), generated_at(t) { ... }` `explicit JobFinishedMsg(FlowId fid, Time t) noexcept : flow_id(fid), finished_at(t)`
### `MgmtKind kind() const noexcept override { ... }` ## `JobFinishedMsg::kind`
`MgmtKind kind() const noexcept override`
## class JobFinishedMsg — public interface ## `MgmtMsg::kind`
`virtual MgmtKind kind() const noexcept = 0`
### `: flow_id(fid), finished_at(t) { ... }` ## `MgmtMsg::~MgmtMsg`
### `MgmtKind kind() const noexcept override { ... }` `virtual ~MgmtMsg() = default`
## class EndSimulationMsg — public interface
### `MgmtKind kind() const noexcept override { ... }`

View File

@@ -1,20 +1,13 @@
# hosts/policies.h # hosts/policies.h
## `PubBasePolicy::select_multicast_groups`
## class PubBasePolicy — public interface `virtual PacketGroups select_multicast_groups(PacketGroups update_groups_mask) = 0`
## `PubBasePolicy::~PubBasePolicy`
### `virtual ~PubBasePolicy() = default;` `virtual ~PubBasePolicy() = default`
## `PubRRPolicy::PubRRPolicy`
## class PubRRPolicy — public interface `explicit PubRRPolicy(std::vector<ReplicaRange> ranges) : _ranges(std::move(ranges))`
## `PubRRPolicy::select_multicast_groups`
### `: _ranges(std::move(ranges)) { ... }` `PacketGroups select_multicast_groups(PacketGroups update_groups_mask) override`
### `validate_and_build();` ## `group_present`
### `PacketGroups select_multicast_groups(PacketGroups update_groups_mask) override { ... }` `static bool group_present(PacketGroups mask, uint32_t gid) noexcept{`
### `for (auto const& r : _ranges) { ... }` ## `validate_and_build`
`void validate_and_build(){`
## class SubBasePolicy — public interface
### `virtual ~SubBasePolicy() = default;`
## class SubDummyPolicy — public interface
### `~SubDummyPolicy() override = default;`

View File

@@ -1,10 +1,25 @@
# hosts/publisher.h # hosts/publisher.h
## `Publisher::Pending::arm_staging_if_needed`
## class Publisher — public interface `void arm_staging_if_needed() noexcept`
## `Publisher::Pending::on_staging_timer`
### `void recv_update(Bytes size, PacketGroups update_groups_mask) noexcept;` `void on_staging_timer() noexcept`
### `void set_status(NodeStatus s, Time new_latency = Time{}) noexcept;` ## `Publisher::Publisher`
### `virtual void recv_mgmt_msg(MgmtMsgPtr msg) noexcept override;` `Publisher(Simulator *sim, NodeId id, Time update_latency_base, std::unique_ptr<PubBasePolicy> policy, Time mgmt_latency) noexcept`
### `virtual void recv_frame(const Packet& frame) override;` ## `Publisher::bytes_out`
### `uint64_t updates_in() const noexcept { ... }` `uint64_t bytes_out() const noexcept`
### `uint64_t bytes_out() const noexcept { ... }` ## `Publisher::recv_flow`
`virtual void recv_flow(NodeId src, FlowId flow, FlowPriority prio, Bytes flow_size) override`
## `Publisher::recv_frame`
`virtual void recv_frame(const Packet &frame) override`
## `Publisher::recv_mgmt_msg`
`virtual void recv_mgmt_msg(MgmtMsgPtr msg) noexcept override`
## `Publisher::recv_update`
`void recv_update(Bytes size, PacketGroups update_groups_mask) noexcept`
## `Publisher::set_status`
`void set_status(NodeStatus s, Time new_latency = Time{}) noexcept`
## `Publisher::updates_in`
`uint64_t updates_in() const noexcept`
## `arm_staging_if_needed`
`void arm_staging_if_needed() noexcept;`
## `on_staging_timer`
`void on_staging_timer() noexcept;`

View File

@@ -1,8 +1,19 @@
# hosts/subscriber.h # hosts/subscriber.h
## `Subscriber::Subscriber`
## class Publisher — public interface `Subscriber(Simulator *sim, NodeId id, Publisher *publisher, std::unique_ptr<SubBasePolicy> policy, Time mgmt_latency, Time heartbeat_period) noexcept`
## `Subscriber::recv_flow`
### `virtual void recv_mgmt_msg(MgmtMsgPtr msg) noexcept override;` `void recv_flow(NodeId src, FlowId flow, FlowPriority prio, Bytes flow_size) override`
### `void recv_frame(const Packet& frame) override;` ## `Subscriber::recv_frame`
### `void set_status(NodeStatus s) noexcept;` `void recv_frame(const Packet &frame) override`
### `void set_publisher(Publisher* p) noexcept { ... }` ## `Subscriber::recv_mgmt_msg`
`virtual void recv_mgmt_msg(MgmtMsgPtr msg) noexcept override`
## `Subscriber::set_publisher`
`void set_publisher(Publisher *p) noexcept`
## `Subscriber::set_status`
`void set_status(NodeStatus s) noexcept`
## `dofs::on_heartbeat_timer`
`void on_heartbeat_timer() noexcept;`
## `dofs::schedule_next_heartbeat`
`void schedule_next_heartbeat(Time delay) noexcept;`
## `dofs::send_job_finished`
`void send_job_finished(FlowId flow) noexcept;`

3899
docs/index.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,37 @@
# network/link.h # network/link.h
## `Link::Link`
## class Link — public interface `Link(Simulator *const sim, LinkId id, NetworkNode *a, PortId a_port, NetworkNode *b, PortId b_port, Time latency, double bandwidth_gbps) noexcept`
## `Link::bandwidth_gbps`
### `void send_pkt(Packet &pkt, NodeId caller);` `double bandwidth_gbps() const noexcept`
### `void schedule_delivery_after(Packet &pkt, NodeId caller, Time after);` ## `Link::dst_id`
### `Time next_available(NodeId sender) const noexcept;` `NodeId dst_id() const noexcept`
### `std::optional<Reservation> reserve(Bytes bytes, NodeId sender) noexcept;` ## `Link::dst_port`
### `Time serialization_time(Bytes bytes) const noexcept { ... }` `PortId dst_port() const noexcept`
### `return serialization_time(bytes, _bandwidth_gbps_cur);` ## `Link::id`
### `Time propagation_latency() const noexcept { ... }` `LinkId id() const noexcept`
### `LinkId id() const noexcept { ... }` ## `Link::next_available`
### `LinkStatus status() const noexcept { ... }` `Time next_available(NodeId sender) const noexcept`
### `double bandwidth_gbps() const noexcept { ... }` ## `Link::propagation_latency`
### `NodeId src_id() const noexcept { ... }` `Time propagation_latency() const noexcept`
### `NodeId dst_id() const noexcept { ... }` ## `Link::reserve`
### `PortId src_port() const noexcept { ... }` `std::optional<Reservation> reserve(Bytes bytes, NodeId sender) noexcept`
### `PortId dst_port() const noexcept { ... }` ## `Link::schedule_delivery_after`
### `static Time serialization_time(Bytes bytes, double gbps) noexcept;` `void schedule_delivery_after(Packet &pkt, NodeId caller, Time after)`
## `Link::send_pkt`
`void send_pkt(Packet &pkt, NodeId caller)`
## `Link::serialization_time`
`Time serialization_time(Bytes bytes) const noexcept`
## `Link::serialization_time`
`static Time serialization_time(Bytes bytes, double gbps) noexcept`
## `Link::set_status`
`void set_status(LinkStatus s, Time new_latency = Time(0), double new_bandwidth_gbps = 0.0)`
## `Link::src_id`
`NodeId src_id() const noexcept`
## `Link::src_port`
`PortId src_port() const noexcept`
## `Link::status`
`LinkStatus status() const noexcept`
## `serialization_time`
`static Time serialization_time(Bytes bytes, double gbps) noexcept;`
## `set_status`
`void set_status(LinkStatus s, Time new_latency = Time(0), double new_bandwidth_gbps = 0.0);`

View File

@@ -1,11 +1,95 @@
# network/network_nic.h # network/network_nic.h
## `NicSchedulingWeights::NetworkNic::NetworkNic`
## class NetworkNic — public interface `NetworkNic(Simulator *const sim, NodeId id, uint16_t total_ports, SwitchBuffer *const buf, Time nic_latency, Bytes mice_elephant_threshold, PacketSeq ooo_threshold, CCType cc_type, LBType lb_type, Bytes cc_init_cwnd, Bytes cc_max_cwnd, const NicSchedulingWeights &schedw) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::enqueue_packet`
### `virtual void recv_pkt(Packet &pkt, PortId ingress) override;` `void enqueue_packet(PortId port, QClass cls, Packet pkt) noexcept`
### `void attach_host(Host* host) noexcept;` ## `NicSchedulingWeights::NetworkNic::PortQueues::is_elephant`
### `void detach_host(Host* host) noexcept;` `bool is_elephant(Bytes sz) const noexcept`
### `void set_status(NodeStatus s, Time new_latency = Time(0)) noexcept;` ## `NicSchedulingWeights::NetworkNic::PortQueues::lookup_rtt_and_erase`
### `const NicTelemetry &telemetry() const noexcept { ... }` `bool lookup_rtt_and_erase(const Packet &ack_like, Time now, Time &out_rtt) noexcept`
### `void set_port_blacklisted(PortId port, bool blacklisted) noexcept;` ## `NicSchedulingWeights::NetworkNic::PortQueues::make_ack_packet`
### `bool is_port_blacklisted(PortId port) const noexcept;` `Packet make_ack_packet(const Packet &rx_data, PortId ingress) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::make_data_packet`
`Packet make_data_packet(NodeId dst, PortId out_port, FlowPriority prio, FlowId fid, PacketSeq seq, Bytes payload) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::make_nack_packet`
`Packet make_nack_packet(NodeId peer, FlowId flow, PacketSeq miss, FlowPriority prio, PortId ingress) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::make_trim_back_response`
`Packet make_trim_back_response(const Packet &trim, PortId ingress) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::pick_next_qclass`
`bool pick_next_qclass(const PortQueues &pq, QClass &out_cls) const noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::pick_src_port_for_flow`
`PortId pick_src_port_for_flow(NodeId dst) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::port_drain_task`
`void port_drain_task(PortId port) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::record_tx_timestamp`
`void record_tx_timestamp(const Packet &pkt) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::schedule_ack`
`void schedule_ack(const Packet &rx_data, PortId ingress) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::schedule_nack`
`void schedule_nack(NodeId peer, FlowId flow, PacketSeq missing_seq, FlowPriority prio, PortId ingress) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::schedule_port_if_needed`
`void schedule_port_if_needed(PortId port) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::schedule_trim_back_response`
`void schedule_trim_back_response(const Packet &trim, PortId ingress) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::start_tx_multicast`
`void start_tx_multicast(PacketGroups gmask, Bytes size, FlowPriority prio)`
## `NicSchedulingWeights::NetworkNic::PortQueues::start_tx_unicast`
`void start_tx_unicast(NodeId dst, Bytes size, FlowPriority prio)`
## `NicSchedulingWeights::NetworkNic::PortQueues::try_send_one`
`bool try_send_one(PortId port, QClass cls) noexcept`
## `NicSchedulingWeights::NetworkNic::PortQueues::txkey`
`static inline std::uint64_t txkey(FlowId f, PacketSeq s) noexcept`
## `NicSchedulingWeights::NetworkNic::attach_host`
`void attach_host(Host *host) noexcept`
## `NicSchedulingWeights::NetworkNic::detach_host`
`void detach_host(Host *host) noexcept`
## `NicSchedulingWeights::NetworkNic::is_port_blacklisted`
`bool is_port_blacklisted(PortId port) const noexcept`
## `NicSchedulingWeights::NetworkNic::recv_pkt`
`virtual void recv_pkt(Packet &pkt, PortId ingress) override`
## `NicSchedulingWeights::NetworkNic::send_flow`
`void send_flow(NodeId dst, Bytes size, FlowPriority desired = FlowPriority::AUTO)`
## `NicSchedulingWeights::NetworkNic::send_flow`
`void send_flow(PacketGroups group_mask, Bytes size, FlowPriority desired = FlowPriority::AUTO)`
## `NicSchedulingWeights::NetworkNic::set_port_blacklisted`
`void set_port_blacklisted(PortId port, bool blacklisted) noexcept`
## `NicSchedulingWeights::NetworkNic::set_status`
`void set_status(NodeStatus s, Time new_latency = Time(0)) noexcept`
## `NicSchedulingWeights::NetworkNic::telemetry`
`const NicTelemetry &telemetry() const noexcept`
## `enqueue_packet`
`void enqueue_packet(PortId port, QClass cls, Packet pkt) noexcept;`
## `lookup_rtt_and_erase`
`bool lookup_rtt_and_erase(const Packet &ack_like, Time now, Time &out_rtt) noexcept;`
## `make_ack_packet`
`Packet make_ack_packet(const Packet &rx_data, PortId ingress) noexcept;`
## `make_data_packet`
`Packet make_data_packet(NodeId dst, PortId out_port, FlowPriority prio, FlowId fid, PacketSeq seq, Bytes payload) noexcept;`
## `make_nack_packet`
`Packet make_nack_packet(NodeId peer, FlowId flow, PacketSeq miss, FlowPriority prio, PortId ingress) noexcept;`
## `make_trim_back_response`
`Packet make_trim_back_response(const Packet &trim, PortId ingress) noexcept;`
## `pick_src_port_for_flow`
`PortId pick_src_port_for_flow(NodeId dst) noexcept;`
## `port_drain_task`
`void port_drain_task(PortId port) noexcept;`
## `record_tx_timestamp`
`void record_tx_timestamp(const Packet &pkt) noexcept;`
## `schedule_ack`
`void schedule_ack(const Packet &rx_data, PortId ingress) noexcept;`
## `schedule_nack`
`void schedule_nack(NodeId peer, FlowId flow, PacketSeq missing_seq, FlowPriority prio, PortId ingress) noexcept;`
## `schedule_port_if_needed`
`void schedule_port_if_needed(PortId port) noexcept;`
## `schedule_trim_back_response`
`void schedule_trim_back_response(const Packet &trim, PortId ingress) noexcept;`
## `set_port_blacklisted`
`void set_port_blacklisted(PortId port, bool blacklisted) noexcept;`
## `start_tx_multicast`
`void start_tx_multicast(PacketGroups gmask, Bytes size, FlowPriority prio);`
## `start_tx_unicast`
`void start_tx_unicast(NodeId dst, Bytes size, FlowPriority prio);`
## `try_send_one`
`bool try_send_one(PortId port, QClass cls) noexcept;`
## `txkey`
`static inline std::uint64_t txkey(FlowId f, PacketSeq s) noexcept;`

View File

@@ -1,6 +1,7 @@
# network/network_node.h # network/network_node.h
## `NetworkNode::NetworkNode`
## class NetworkNode — public interface `explicit NetworkNode(Simulator *const sim, NodeId id, NodeType type) noexcept`
## `NetworkNode::recv_pkt`
### `explicit NetworkNode(Simulator *const sim, NodeId id, NodeType type) noexcept;` `virtual void recv_pkt(Packet &pkt, PortId ingress) = 0`
### `virtual ~NetworkNode() = default;` ## `NetworkNode::~NetworkNode`
`virtual ~NetworkNode() = default`

View File

@@ -1,9 +1,19 @@
# network/network_switch.h # network/network_switch.h
## `NetworkSwitch::NetworkSwitch`
## class NetworkSwitch — public interface `NetworkSwitch(Simulator *const sim, NodeId id, uint16_t total_ports, ECNEngine *const ecn, SwitchBuffer *const buf, const RoutingTables *const rt, Time forwarding_latency, Time multicast_dup_delay) noexcept`
## `NetworkSwitch::get_status`
### `virtual void recv_pkt(Packet &pkt, PortId ingress) override;` `NodeStatus get_status() const noexcept`
### `void set_status(NodeStatus s, Time new_forward_latency = Time(0)) noexcept;` ## `NetworkSwitch::port_cnt`
### `NodeStatus get_status() const noexcept { ... }` `uint16_t port_cnt() const noexcept`
### `return status();` ## `NetworkSwitch::recv_pkt`
### `uint16_t port_cnt() const noexcept { ... }` `virtual void recv_pkt(Packet &pkt, PortId ingress) override`
## `NetworkSwitch::set_status`
`void set_status(NodeStatus s, Time new_forward_latency = Time(0)) noexcept`
## `enqueue_one`
`void enqueue_one(Packet pkt, PortId egress, FlowPriority prio) noexcept;`
## `forward_after_delay`
`private: void forward_after_delay(Packet pkt, PortId ingress) noexcept;`
## `group_bit_set`
`static inline bool group_bit_set(PacketGroups mask, std::size_t gid) noexcept;`
## `merge_sorted_unique`
`static void merge_sorted_unique(std::vector<PortId> &base, const std::vector<PortId> &add);`

View File

@@ -1,18 +1,21 @@
# network/nic/congestion_control.h # network/nic/congestion_control.h
## `CongestionControl::CongestionControl`
## class CongestionControl — public interface `explicit CongestionControl(Bytes init_cwnd, Bytes max_cwnd) noexcept`
## `CongestionControl::cwnd`
### `explicit CongestionControl(Bytes init_cwnd, Bytes max_cwnd) noexcept;` `Bytes cwnd() const noexcept`
### `virtual ~CongestionControl() = default;` ## `CongestionControl::cwnd_max`
### `Bytes cwnd() const noexcept { ... }` `Bytes cwnd_max() const noexcept`
### `Bytes cwnd_max() const noexcept { ... }` ## `CongestionControl::is_allowed_to_send`
`virtual bool is_allowed_to_send(Bytes bytes_outstanding, Bytes next_bytes) const noexcept`
## class DCQCN — public interface ## `CongestionControl::update`
`virtual void update(const Packet &pkt, Time rtt) noexcept = 0`
### `explicit DCQCN(Bytes init_cwnd, Bytes max_cwnd) noexcept;` ## `CongestionControl::~CongestionControl`
### `virtual void update(const Packet& pkt, Time rtt) noexcept override;` `virtual ~CongestionControl() = default`
## `DCQCN::DCQCN`
## class NSCC — public interface `explicit DCQCN(Bytes init_cwnd, Bytes max_cwnd) noexcept`
## `DCQCN::update`
### `explicit NSCC(Bytes init_cwnd, Bytes max_cwnd) noexcept;` `virtual void update(const Packet &pkt, Time rtt) noexcept override`
### `virtual void update(const Packet& pkt, Time rtt) noexcept override;` ## `NSCC::NSCC`
`explicit NSCC(Bytes init_cwnd, Bytes max_cwnd) noexcept`
## `NSCC::update`
`virtual void update(const Packet &pkt, Time rtt) noexcept override`

View File

@@ -1,12 +1,15 @@
# network/nic/load_balance.h # network/nic/load_balance.h
## `LBRandomPacketSpraying::LBRandomPacketSpraying`
## class LoadBalance — public interface `explicit LBRandomPacketSpraying(Rng *const rng) noexcept : LoadBalance(rng)`
## `LBRandomPacketSpraying::get_entropy`
### `explicit LoadBalance(Rng *const rng) noexcept : _rng(rng) { ... }` `virtual uint16_t get_entropy(const Packet &context) noexcept override`
### `virtual ~LoadBalance() = default;` ## `LBRandomPacketSpraying::update`
`virtual void update(const Packet &pkt) noexcept override`
## class LBRandomPacketSpraying — public interface ## `LoadBalance::LoadBalance`
`explicit LoadBalance(Rng *const rng) noexcept : _rng(rng)`
### `explicit LBRandomPacketSpraying(Rng *const rng) noexcept : LoadBalance(rng) { ... }` ## `LoadBalance::get_entropy`
### `virtual void update(const Packet& pkt) noexcept override;` `virtual uint16_t get_entropy(const Packet &context) noexcept = 0`
### `virtual uint16_t get_entropy(const Packet& context) noexcept override;` ## `LoadBalance::update`
`virtual void update(const Packet &pkt) noexcept = 0`
## `LoadBalance::~LoadBalance`
`virtual ~LoadBalance() = default`

View File

@@ -0,0 +1,2 @@
# network/nic/nic_telemetry.h
_No public API symbols found in this header._

View File

@@ -1,35 +1,65 @@
# network/packet.h # network/packet.h
## `Packet::Packet`
## class Packet — public interface `Packet(NodeId src_node, PortId src_port, NodeId dst_node, PortId dst_port, PacketProtocol proto, FlowPriority prio, PacketSeq seq = 0, FlowId flow = 0, uint16_t entropy = 0, uint8_t notifications = 0, Bytes payload_bytes = DEFAULT_MSS_BYTES) noexcept`
## `Packet::add_groups`
### `NodeId src_node() const noexcept;` `void add_groups(PacketGroups gmask) noexcept`
### `PortId src_port() const noexcept;` ## `Packet::dst_node`
### `NodeId dst_node() const noexcept;` `NodeId dst_node() const noexcept`
### `PortId dst_port() const noexcept;` ## `Packet::dst_port`
### `PacketProtocol protocol() const noexcept;` `PortId dst_port() const noexcept`
### `PacketSeq seq() const noexcept;` ## `Packet::entropy`
### `FlowId flow_id() const noexcept;` `uint32_t entropy() const noexcept`
### `uint32_t entropy() const noexcept;` ## `Packet::flow_id`
### `void set_src_node(NodeId n) noexcept;` `FlowId flow_id() const noexcept`
### `void set_src_port(PortId p) noexcept;` ## `Packet::groups`
### `void set_dst_node(NodeId n) noexcept;` `PacketGroups groups() const noexcept`
### `void set_dst_port(PortId p) noexcept;` ## `Packet::header_size`
### `void set_seq(PacketSeq s) noexcept;` `Bytes header_size() const noexcept`
### `void set_flow_id(FlowId f) noexcept;` ## `Packet::is_ecn`
### `void set_entropy(uint32_t e) noexcept;` `bool is_ecn() const noexcept`
### `void set_protocol(PacketProtocol p) noexcept;` ## `Packet::is_ecn_enabled`
### `void set_payload_size(Bytes size) noexcept;` `bool is_ecn_enabled() const noexcept`
### `void set_ecn_enabled(bool v) noexcept;` ## `Packet::is_eof`
### `void set_ecn_marked(bool v) noexcept;` `bool is_eof() const noexcept`
### `void set_eof(bool v) noexcept;` ## `Packet::payload_size`
### `bool is_ecn_enabled() const noexcept;` `Bytes payload_size() const noexcept`
### `bool is_ecn() const noexcept;` ## `Packet::priority`
### `bool is_eof() const noexcept;` `FlowPriority priority() const noexcept`
### `FlowPriority priority() const noexcept;` ## `Packet::priority_raw`
### `uint8_t priority_raw() const noexcept;` `uint8_t priority_raw() const noexcept`
### `Bytes header_size() const noexcept;` ## `Packet::protocol`
### `Bytes payload_size() const noexcept;` `PacketProtocol protocol() const noexcept`
### `Bytes total_size() const noexcept;` ## `Packet::seq`
### `PacketGroups groups() const noexcept;` `PacketSeq seq() const noexcept`
### `void set_groups(PacketGroups g) noexcept;` ## `Packet::set_dst_node`
### `void add_groups(PacketGroups gmask) noexcept;` `void set_dst_node(NodeId n) noexcept`
## `Packet::set_dst_port`
`void set_dst_port(PortId p) noexcept`
## `Packet::set_ecn_enabled`
`void set_ecn_enabled(bool v) noexcept`
## `Packet::set_ecn_marked`
`void set_ecn_marked(bool v) noexcept`
## `Packet::set_entropy`
`void set_entropy(uint32_t e) noexcept`
## `Packet::set_eof`
`void set_eof(bool v) noexcept`
## `Packet::set_flow_id`
`void set_flow_id(FlowId f) noexcept`
## `Packet::set_groups`
`void set_groups(PacketGroups g) noexcept`
## `Packet::set_payload_size`
`void set_payload_size(Bytes size) noexcept`
## `Packet::set_protocol`
`void set_protocol(PacketProtocol p) noexcept`
## `Packet::set_seq`
`void set_seq(PacketSeq s) noexcept`
## `Packet::set_src_node`
`void set_src_node(NodeId n) noexcept`
## `Packet::set_src_port`
`void set_src_port(PortId p) noexcept`
## `Packet::src_node`
`NodeId src_node() const noexcept`
## `Packet::src_port`
`PortId src_port() const noexcept`
## `Packet::total_size`
`Bytes total_size() const noexcept`

View File

@@ -1,5 +1,7 @@
# network/switch/dedicated_buffer.h # network/switch/dedicated_buffer.h
## `DedicatedBuffer::DedicatedBuffer`
## class DedicatedBuffer — public interface `DedicatedBuffer(Simulator *const sim, NetworkSwitch *const owner, Bytes total_bytes, uint16_t ports)`
## `DedicatedBuffer::drain_one`
### `bool drain_one(PortId port) override;` `bool drain_one(PortId port) override`
## `DedicatedBuffer::enqueue_packet`
`bool enqueue_packet(const Packet &pkt, PortId egress, FlowPriority prio) override`

View File

@@ -1,5 +1,7 @@
# network/switch/ecn_dedicated_red.h # network/switch/ecn_dedicated_red.h
## `DedicatedREDEngine::DedicatedREDEngine`
## class DedicatedREDEngine — public interface `DedicatedREDEngine(Bytes min_th, Bytes max_th, double p_max, bool back_to_sender, Rng *const rng) noexcept : _min_th(min_th), _max_th(max_th), _p_max(p_max), _back_to_sender(back_to_sender), _rng(rng)`
## `DedicatedREDEngine::process_packet`
### `_rng(rng) { ... }` `virtual Packet &process_packet(Packet &pkt, SwitchBuffer *buf) noexcept override`
## `dofs::ensure_size`
`private: void ensure_size(uint16_t port_cnt){`

View File

@@ -1,5 +1,7 @@
# network/switch/ecn_engine.h # network/switch/ecn_engine.h
## `ECNEngine::process_packet`
## class SwitchBuffer — public interface `virtual Packet &process_packet(Packet &pkt, SwitchBuffer *buf) noexcept = 0`
## `ECNEngine::~ECNEngine`
### `virtual ~ECNEngine() = default;` `virtual ~ECNEngine() = default`
## `dofs::header_trim`
`static inline void header_trim(Packet &pkt, bool back_to_sender) noexcept{`

View File

@@ -1,5 +1,13 @@
# network/switch/ecn_shared_red.h # network/switch/ecn_shared_red.h
## `SharedREDEngine::SharedREDEngine`
## class SharedREDEngine — public interface `explicit SharedREDEngine(Rng *const rng = nullptr) noexcept : _rng(rng), _avg_total_bytes(0.0), _avg_port_bytes()`
## `SharedREDEngine::process_packet`
### `_avg_port_bytes() { ... }` `virtual Packet &process_packet(Packet &pkt, SwitchBuffer *buf) noexcept override`
## `dofs::ensure_size`
`private: void ensure_size(uint16_t port_cnt){`
## `is_ctrl`
`static inline bool is_ctrl(const Packet &p) noexcept{`
## `is_ele`
`static inline bool is_ele (const Packet &p) noexcept{`
## `is_mice`
`static inline bool is_mice(const Packet &p) noexcept{`

View File

@@ -1,15 +1,23 @@
# network/switch/multicast_table.h # network/switch/multicast_table.h
## `McTree::MulticastTable::MulticastTable`
## class MulticastTable — public interface `MulticastTable()`
## `McTree::MulticastTable::add_child_port`
### `MulticastTable();` `bool add_child_port(std::size_t group_id, uint16_t tree_id, PortId out_port)`
### `bool add_tree(std::size_t group_id, uint16_t tree_id);` ## `McTree::MulticastTable::add_tree`
### `bool delete_tree(std::size_t group_id, uint16_t tree_id);` `bool add_tree(std::size_t group_id, uint16_t tree_id)`
### `bool add_child_port(std::size_t group_id, uint16_t tree_id, PortId out_port);` ## `McTree::MulticastTable::delete_child_port`
### `bool delete_child_port(std::size_t group_id, uint16_t tree_id, PortId out_port);` `bool delete_child_port(std::size_t group_id, uint16_t tree_id, PortId out_port)`
### `bool set_parent(std::size_t group_id, uint16_t tree_id, PortId parent);` ## `McTree::MulticastTable::delete_tree`
### `bool set_weight(std::size_t group_id, uint16_t tree_id, uint8_t w);` `bool delete_tree(std::size_t group_id, uint16_t tree_id)`
### `bool set_epoch(std::size_t group_id, uint16_t tree_id, uint8_t epoch);` ## `McTree::MulticastTable::get_port_list`
### `const std::vector<McTree> *trees_of(std::size_t group_id) const;` `std::vector<PortId> get_port_list(PacketGroups groups) const`
### `std::size_t group_count() const noexcept;` ## `McTree::MulticastTable::group_count`
### `std::vector<PortId> get_port_list(PacketGroups groups) const;` `std::size_t group_count() const noexcept`
## `McTree::MulticastTable::set_epoch`
`bool set_epoch(std::size_t group_id, uint16_t tree_id, uint8_t epoch)`
## `McTree::MulticastTable::set_parent`
`bool set_parent(std::size_t group_id, uint16_t tree_id, PortId parent)`
## `McTree::MulticastTable::set_weight`
`bool set_weight(std::size_t group_id, uint16_t tree_id, uint8_t w)`
## `McTree::MulticastTable::trees_of`
`const std::vector<McTree> *trees_of(std::size_t group_id) const`

View File

@@ -0,0 +1,7 @@
# network/switch/routing_alg.h
## `dofs::hash_ecmp`
`PortId hash_ecmp(NodeId src_node, PortId src_port, NodeId dst_node, PortId dst_port, uint32_t differentiator, uint16_t port_count) noexcept;`
## `dofs::hash_ecmp`
`inline PortId hash_ecmp(NodeId src_node, NodeId dst_node, uint32_t differentiator, uint16_t port_count) noexcept{`
## `dofs::hash_ecmp`
`return hash_ecmp(src_node, static_cast<PortId>(0), dst_node, static_cast<PortId>(0), differentiator, port_count);`

View File

@@ -0,0 +1,2 @@
# network/switch/routing_tables.h
_No public API symbols found in this header._

View File

@@ -1,5 +1,7 @@
# network/switch/shared_buffer.h # network/switch/shared_buffer.h
## `SharedBuffer::SharedBuffer`
## class SharedBuffer — public interface `SharedBuffer(Simulator *const sim, NetworkSwitch *const owner, Bytes total_bytes, uint16_t ports)`
## `SharedBuffer::drain_one`
### `virtual bool drain_one(PortId port) override;` `virtual bool drain_one(PortId port) override`
## `SharedBuffer::enqueue_packet`
`virtual bool enqueue_packet(const Packet &pkt, PortId egress, FlowPriority prio) override`

View File

@@ -1,19 +1,65 @@
# network/switch/switch_buffer.h # network/switch/switch_buffer.h
## `SwitchBuffer::PerPortSched::drain_once`
## class NetworkSwitch — public interface `void drain_once(PortId port)`
## `SwitchBuffer::PerPortSched::drain_one_common`
### `virtual ~SwitchBuffer() = default;` `bool drain_one_common(PortId port)`
### `Bytes buffer_size() const noexcept;` ## `SwitchBuffer::PerPortSched::on_dequeue_commit`
### `SwitchBufferType type() const noexcept;` `virtual void on_dequeue_commit(PortId port, Bytes sz) = 0`
### `uint16_t port_cnt() const noexcept;` ## `SwitchBuffer::PerPortSched::on_enqueue_cap_check`
### `Bytes port_buffered(PortId p) const noexcept;` `virtual bool on_enqueue_cap_check(PortId port, Bytes sz) = 0`
### `const std::vector<Bytes> &ports_buffered() const noexcept;` ## `SwitchBuffer::PerPortSched::on_enqueue_commit`
### `uint8_t share_ctrl() const noexcept;` `virtual void on_enqueue_commit(PortId port, Bytes sz) = 0`
### `uint8_t share_mice() const noexcept;` ## `SwitchBuffer::PerPortSched::queued_bytes_port`
### `uint8_t share_elephant() const noexcept;` `Bytes queued_bytes_port(PortId p) const noexcept`
### `void set_share_ctrl(uint8_t pct) noexcept;` ## `SwitchBuffer::PerPortSched::queued_bytes_total`
### `void set_share_mice(uint8_t pct) noexcept;` `Bytes queued_bytes_total() const noexcept`
### `void set_share_elephant(uint8_t pct) noexcept;` ## `SwitchBuffer::PerPortSched::queues_for`
### `const Simulator *simulator() const noexcept;` `virtual const std::array<std::deque<Queued>, PRI_COUNT> &queues_for( PortId p) const = 0`
### `const NetworkSwitch *owner() const noexcept;` ## `SwitchBuffer::PerPortSched::queues_for`
### `void set_egress_links(const std::vector<Link*> &links);` `virtual std::array<std::deque<Queued>, PRI_COUNT> &queues_for(PortId p) = 0`
## `SwitchBuffer::PerPortSched::schedule_drain_if_needed`
`void schedule_drain_if_needed(PortId port)`
## `SwitchBuffer::PerPortSched::try_reserve_and_send`
`std::optional<Time> try_reserve_and_send(PortId port, Queued &q)`
## `SwitchBuffer::buffer_size`
`Bytes buffer_size() const noexcept`
## `SwitchBuffer::drain_one`
`virtual bool drain_one(PortId port) = 0`
## `SwitchBuffer::enqueue_packet`
`virtual bool enqueue_packet(const Packet &pkt, PortId egress, FlowPriority prio) = 0`
## `SwitchBuffer::owner`
`const NetworkSwitch *owner() const noexcept`
## `SwitchBuffer::port_buffered`
`Bytes port_buffered(PortId p) const noexcept`
## `SwitchBuffer::port_cnt`
`uint16_t port_cnt() const noexcept`
## `SwitchBuffer::ports_buffered`
`const std::vector<Bytes> &ports_buffered() const noexcept`
## `SwitchBuffer::set_egress_links`
`void set_egress_links(const std::vector<Link*> &links)`
## `SwitchBuffer::set_share_ctrl`
`void set_share_ctrl(uint8_t pct) noexcept`
## `SwitchBuffer::set_share_elephant`
`void set_share_elephant(uint8_t pct) noexcept`
## `SwitchBuffer::set_share_mice`
`void set_share_mice(uint8_t pct) noexcept`
## `SwitchBuffer::share_ctrl`
`uint8_t share_ctrl() const noexcept`
## `SwitchBuffer::share_elephant`
`uint8_t share_elephant() const noexcept`
## `SwitchBuffer::share_mice`
`uint8_t share_mice() const noexcept`
## `SwitchBuffer::simulator`
`const Simulator *simulator() const noexcept`
## `SwitchBuffer::type`
`SwitchBufferType type() const noexcept`
## `SwitchBuffer::~SwitchBuffer`
`virtual ~SwitchBuffer() = default`
## `drain_once`
`void drain_once(PortId port);`
## `drain_one_common`
`bool drain_one_common(PortId port);`
## `schedule_drain_if_needed`
`void schedule_drain_if_needed(PortId port);`
## `try_reserve_and_send`
`std::optional<Time> try_reserve_and_send(PortId port, Queued &q);`

View File

@@ -1,8 +1,9 @@
# network/switch/unicast_table.h # network/switch/unicast_table.h
## `UnicastTable::UnicastTable`
## class UnicastTable — public interface `UnicastTable() = default`
## `UnicastTable::add_entry`
### `UnicastTable() = default;` `bool add_entry(NodeId dst_node, PortId dst_port, PortId out_port)`
### `std::vector<PortId> get_port_list(NodeId dst_node, PortId dst_port) const;` ## `UnicastTable::delete_entry`
### `bool add_entry(NodeId dst_node, PortId dst_port, PortId out_port);` `bool delete_entry(NodeId dst_node, PortId dst_port, PortId out_port)`
### `bool delete_entry(NodeId dst_node, PortId dst_port, PortId out_port);` ## `UnicastTable::get_port_list`
`std::vector<PortId> get_port_list(NodeId dst_node, PortId dst_port) const`

View File

@@ -28,13 +28,11 @@ else()
logger.h logger.h
simulator.h simulator.h
node.h node.h
host.h
PRIVATE PRIVATE
error.cc error.cc
logger.cc logger.cc
simulator.cc simulator.cc
node.cc node.cc
host.cc
) )
endif() endif()

View File

@@ -7,8 +7,8 @@
#include "core/time.h" #include "core/time.h"
#include "core/types.h" #include "core/types.h"
#include "core/host.h"
#include "core/simulator.h" #include "core/simulator.h"
#include "network/host.h"
#include "hosts/mgmt_msg.h" #include "hosts/mgmt_msg.h"
#include "hosts/policies.h" #include "hosts/policies.h"

View File

@@ -3,8 +3,8 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include "core/host.h"
#include "core/simulator.h" #include "core/simulator.h"
#include "network/host.h"
#include "hosts/mgmt_msg.h" #include "hosts/mgmt_msg.h"
#include "hosts/policies.h" #include "hosts/policies.h"

View File

@@ -22,12 +22,14 @@ else()
network_node.h network_node.h
network_switch.h network_switch.h
network_nic.h network_nic.h
host.h
PRIVATE PRIVATE
packet.cc packet.cc
link.cc link.cc
network_node.cc network_node.cc
network_switch.cc network_switch.cc
network_nic.cc network_nic.cc
host.cc
) )
endif() endif()

View File

@@ -1,6 +1,7 @@
#include "core/host.h" #include "network/host.h"
#include "core/error.h" #include "core/error.h"
#include "network/network_nic.h"
namespace dofs { namespace dofs {
@@ -16,6 +17,7 @@ void Host::attach_nic(NetworkNic *nic) noexcept {
} }
_nic = nic; _nic = nic;
_nic->attach_host(this);
} }
void Host::detach_nic(NetworkNic *nic) noexcept { void Host::detach_nic(NetworkNic *nic) noexcept {
@@ -24,6 +26,7 @@ void Host::detach_nic(NetworkNic *nic) noexcept {
return; return;
} }
_nic->detach_host(this);
_nic = nullptr; _nic = nullptr;
} }

View File

@@ -1,5 +1,5 @@
#ifndef CORE_HOST_H #ifndef NETWORK_HOST_H
#define CORE_HOST_H #define NETWORK_HOST_H
#include <cstdint> #include <cstdint>
@@ -45,4 +45,4 @@ private:
} // namespace dofs } // namespace dofs
#endif // CORE_HOST_H #endif // NETWORK_HOST_H

View File

@@ -2,7 +2,7 @@
#include <algorithm> #include <algorithm>
#include <utility> #include <utility>
#include "core/host.h" #include "network/host.h"
#include "core/error.h" #include "core/error.h"
namespace dofs { namespace dofs {

View File

@@ -1,255 +1,626 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" # dofs/tools/extract_api.py
extract_api.py # Extract DOFS public API from headers and emit:
Scan C++ headers in ./src for public interfaces (public class/struct methods # - Per-header Markdown files under docs/<same path>.md (mirrors src tree)
and free function declarations) and emit markdown snippets mirroring the src/ # - One JSON index at docs/index.json
tree into ./docs.
Each interface line is rendered as:
### `signature`
Usage:
cd dofs
python3 tools/extract_api.py
"""
from __future__ import annotations
import argparse
import json
import re import re
import sys from dataclasses import dataclass, asdict
from pathlib import Path from pathlib import Path
from typing import List, Optional, Tuple, Dict
REPO_ROOT = Path(__file__).resolve().parents[1] # -------- Repo roots --------
SRC_DIR = REPO_ROOT / "src" def _detect_repo_root() -> Path:
DOCS_DIR = REPO_ROOT / "docs" p = Path(__file__).resolve()
for anc in [p.parent, *p.parents]:
if (anc / "src").is_dir():
return anc
return p.parent
HEADER_EXTS = {".h", ".hh", ".hpp", ".hxx"} REPO_ROOT = _detect_repo_root() # .../dofs
SRC_ROOT = REPO_ROOT / "src"
# ---------- Utilities ---------- OUT_DIR_DEFAULT = REPO_ROOT / "docs" # mirror into docs/
# -------- IO helpers --------
def read_text(p: Path) -> str: def read_text(p: Path) -> str:
try: return p.read_text(encoding="utf-8", errors="ignore")
return p.read_text(encoding="utf-8", errors="ignore")
except Exception as e:
print(f"[WARN] failed reading {p}: {e}", file=sys.stderr)
return ""
def strip_comments(code: str) -> str: def iter_headers(root: Path) -> List[Path]:
# Remove /* ... */ (including multiline) and // ... to end of line return sorted(root.rglob("*.h"))
no_block = re.sub(r"/\*.*?\*/", "", code, flags=re.S)
no_line = re.sub(r"//.*?$", "", no_block, flags=re.M)
return no_line
def collapse_ws(s: str) -> str: def strip_comments_and_literals(code: str) -> str:
return re.sub(r"\s+", " ", s).strip() string_re = r'("([^"\\]|\\.)*")|(\'([^\'\\]|\\.)*\')'
slc_re = r'//[^\n]*'
mlc_re = r'/\*.*?\*/'
def _keep_nls(m): # keep line count stable
return re.sub(r'[^\n]', ' ', m.group(0))
code = re.sub(mlc_re, _keep_nls, code, flags=re.S)
code = re.sub(string_re, _keep_nls, code, flags=re.S)
code = re.sub(slc_re, _keep_nls, code)
return code
def ensure_dir(path: Path): # -------- Data model --------
path.parent.mkdir(parents=True, exist_ok=True) @dataclass
class Symbol:
kind: str # "free_function" | "method" | "ctor" | "dtor" | "conversion" | "macro"
qualified: str
signature: str
file: str # e.g., "src/core/simulator.h"
line: int
static: bool = False
const: bool = False
ref_qual: str = ""
template_params: str = ""
# ---------- Heuristic extractors ---------- # -------- Parser (same core as before; trimmed comments) --------
class Parser:
def __init__(self, text: str, relpath: str):
self.text = text; self.relpath = relpath
self.i = 0; self.n = len(text); self.line = 1
self.ns_stack: List[str] = []
self.class_stack: List[dict] = []
self.depth_brace = 0
self.pending_template: Optional[str] = None
self.syms: List[Symbol] = []
ACCESS_RE = re.compile(r"^\s*(public|private|protected)\s*:\s*$") # simple guard for bogus names when we fall into bodies
CLASS_START_RE = re.compile(r"^\s*(class|struct)\s+([A-Za-z_]\w*)\b") self._kw_block = {
POSSIBLE_FUNC_DECL_RE = re.compile( "if", "for", "while", "switch", "return", "case", "default",
r"""^[^;{}()]*\b "do", "else", "break", "continue", "goto", "try", "catch"
(?!typedef\b)(?!using\b)(?!friend\b) }
[A-Za-z_~]\w*\s*
\(
[^;]* # params
\)
(?:\s*(?:const|noexcept|override|final|=0|=\s*default|=\s*delete))*\s*
;
\s*$""",
re.X,
)
POSSIBLE_INLINE_DEF_RE = re.compile(
r"""^[^;{}()]*\b
(?!typedef\b)(?!using\b)(?!friend\b)
[A-Za-z_~]\w*\s*
\(
[^{;]* # params
\)
(?:\s*(?:const|noexcept|override|final))*\s*
\{""",
re.X,
)
SKIP_PREFIXES = ("#define", "#include", "static_assert", "enum ", "enum class ",
"template<", "namespace ", "using ", "typedef ", "friend ",
"struct ", "class ")
def extract_public_methods(lines, is_struct_default_public: bool): def peek(self, k=0): j=self.i+k; return self.text[j] if 0<=j<self.n else ""
public = is_struct_default_public def advance(self, k=1):
out = [] for _ in range(k):
depth = 0 if self.i>=self.n: return
for raw in lines: ch=self.text[self.i]; self.i+=1
line = raw.strip() if ch=="\n": self.line+=1
if not line: def skip_ws(self):
continue while self.i<self.n and self.text[self.i].isspace(): self.advance(1)
# Track nested braces to avoid confusing nested scopes def run(self):
depth += raw.count("{") while self.i < self.n:
depth -= raw.count("}") self.skip_ws()
if self.i >= self.n: break
if self.text.startswith("namespace", self.i): self._parse_namespace(); continue
if self.text.startswith("class ", self.i) or self.text.startswith("struct ", self.i): self._parse_record(); continue
if self.text.startswith("template", self.i):
self.pending_template = self._parse_template_intro(); continue
if self.text.startswith("public:", self.i): self._set_access("public"); self.advance(len("public:")); continue
if self.text.startswith("private:", self.i): self._set_access("private"); self.advance(len("private:")); continue
if self.text.startswith("protected:", self.i): self._set_access("protected"); self.advance(len("protected:")); continue
ch=self.peek()
if ch=="{": self.depth_brace+=1; self.advance(1); continue
if ch=="}": self.depth_brace-=1; self.advance(1); self._maybe_pop(); continue
self._maybe_decl_or_def()
return self.syms
m = ACCESS_RE.match(line) def _skip_balanced_block(self):
if m and depth >= 0: """
public = (m.group(1) == "public") Consume a balanced {...} block starting at the current position
continue (which must be at '{'). This does NOT touch self.depth_brace /
class_stack, so it won't confuse outer block tracking.
"""
if self.peek() != "{":
return
depth = 0
# consume the first '{'
self.advance(1)
depth += 1
while self.i < self.n and depth > 0:
ch = self.peek()
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
self.advance(1)
if not public: # --- blocks ---
continue def _parse_namespace(self):
self.advance(len("namespace")); self.skip_ws()
if line.startswith(SKIP_PREFIXES) or line.endswith(":"): if self.text.startswith("inline", self.i):
continue self.advance(len("inline")); self.skip_ws()
m = re.match(r'([A-Za-z_]\w*(::[A-Za-z_]\w*)*)?', self.text[self.i:])
if POSSIBLE_FUNC_DECL_RE.match(line): name = "";
out.append(collapse_ws(line)) if m: name = m.group(0) or ""; self.advance(len(name))
continue self.skip_ws()
if self.peek() == "{":
if POSSIBLE_INLINE_DEF_RE.match(line): self.advance(1); self.depth_brace += 1
sig = line.split("{", 1)[0].rstrip() self.ns_stack.append(name if name else "")
out.append(collapse_ws(sig) + " { ... }") def _parse_record(self):
continue kw = "class" if self.text.startswith("class ", self.i) else "struct"
self.advance(len(kw)); self.skip_ws()
return out name = self._read_word()
if not name: return
def extract_free_function_decls(code: str): while self.i<self.n and self.peek() not in "{;":
# Remove class/struct bodies to avoid capturing methods if self.peek()=="<": self._read_balanced("<", ">")
scrubbed = [] else: self.advance(1)
toks = code.splitlines() if self.peek()=="{":
in_class = False self.advance(1); self.depth_brace += 1
brace_balance = 0 self.class_stack.append({"name": name, "access": "public" if kw=="struct" else "private", "brace_depth": self.depth_brace})
for line in toks:
if not in_class:
if CLASS_START_RE.match(line):
in_class = True
brace_balance = line.count("{") - line.count("}")
scrubbed.append("")
continue
else: else:
brace_balance += line.count("{") - line.count("}") self.advance(1) # forward decl
if brace_balance <= 0:
in_class = False def _parse_template_intro(self) -> str:
scrubbed.append("") self.advance(len("template")); self.skip_ws()
params = self._read_balanced("<", ">") if self.peek()=="<" else ""
return f"template{params}"
def _set_access(self, acc: str):
if self.class_stack: self.class_stack[-1]["access"]=acc
def _maybe_pop(self):
if self.class_stack and self.class_stack[-1]["brace_depth"] == self.depth_brace + 1:
self.class_stack.pop(); return
if self.ns_stack: self.ns_stack.pop()
# --- helpers ---
def _read_word(self) -> str:
self.skip_ws()
m = re.match(r'[A-Za-z_]\w*', self.text[self.i:])
if not m: return ""
w = m.group(0); self.advance(len(w)); return w
def _read_balanced(self, o: str, c: str) -> str:
depth=1; out=o; self.advance(1)
while self.i<self.n and depth>0:
ch=self.peek(); out+=ch; self.advance(1)
if ch==o: depth+=1
elif ch==c: depth-=1
return out
def _current_ns_is_dofs(self) -> bool:
if not self.ns_stack: return False
chain=[p for p in self.ns_stack if p]
return bool(chain) and chain[0]=="dofs"
def _read_one_head(self) -> Tuple[str, str]:
par=ang=sq=0; start=self.i
while self.i<self.n:
ch=self.peek()
if ch=="(": par+=1
elif ch==")": par=max(0,par-1)
elif ch=="<": ang+=1
elif ch==">": ang=max(0,ang-1)
elif ch=="[": sq+=1
elif ch=="]": sq=max(0,sq-1)
elif ch==";" and par==0 and ang==0 and sq==0:
end=self.i; self.advance(1)
return self.text[start:end].strip(), ";"
elif ch=="{" and par==0 and ang==0 and sq==0:
end=self.i
return self.text[start:end].strip(), "{"
self.advance(1)
return "", ""
def _skip_brace_block(self):
"""Assumes current char is '{'; skis balanced block."""
if self.peek() != "{":
return
brace = 0
while self.i < self.n:
ch = self.peek()
self.advance(1)
if ch == "{":
brace += 1
elif ch == "}":
brace -= 1
if brace == 0:
break
def _consume_until_sep(self):
par=ang=sq=0
while self.i<self.n:
ch=self.peek(); self.advance(1)
if ch=="(": par+=1
elif ch==")": par=max(0,par-1)
elif ch=="<": ang+=1
elif ch==">": ang=max(0,ang-1)
elif ch=="[": sq+=1
elif ch=="]": sq=max(0,sq-1)
elif ch==";" and par==0 and ang==0 and sq==0: return
elif ch=="{" and par==0 and ang==0 and sq==0:
brace=1
while self.i<self.n and brace>0:
c2=self.peek(); self.advance(1)
if c2=="{": brace+=1
elif c2=="}": brace-=1
return
def _maybe_decl_or_def(self):
start_line = self.line
# skip obvious non-function starts
for bs in ("using ", "typedef ", "enum ", "namespace ", "static_assert"):
if self.text.startswith(bs, self.i):
self._consume_until_sep(); return
if self.text.startswith("template ", self.i):
self.pending_template = self._parse_template_intro(); return
decl, endch = self._read_one_head()
if not decl.strip(): return
tparams = self.pending_template or ""
self.pending_template = None
if "friend" in decl: return
if "(" not in decl or ")" not in decl: return
recorded = False
# classify: method vs free fn (inside dofs)
in_class = bool(self.class_stack)
if in_class:
if self.class_stack[-1]["access"] != "public": return
self._record_method(decl, start_line, tparams)
recorded = True
else:
if self._current_ns_is_dofs():
self._record_free_function(decl, start_line, tparams)
recorded = True
# If we just read a function head with a body, skip the body **after** recording
if endch == "{":
self._skip_brace_block()
return
# If it wasn't recorded (e.g., not in dofs namespace for free function),
# just continue; declarations ending with ';' need no additional skipping.
if recorded:
return
else:
return
# --- symbol building ---
def _normalize(self, s: str) -> str:
return re.sub(r'\s+', ' ', s).strip()
def _name_from_decl(self, decl: str) -> str:
"""
Find the function/method name robustly:
- choose the '(' that starts the *parameter list* (angle-depth == 0)
- then take the identifier immediately to its left as the name
Avoids mistaking template args like 'std::function<void()>' for a function.
"""
# Strip trailing qualifiers after param list for stability
head = re.split(r'\b(noexcept|requires)\b', decl)[0]
# Scan to find the '(' that begins the parameter list at angle-depth 0
ang = 0
par_open_idx = -1
for idx, ch in enumerate(head):
if ch == '<':
ang += 1
elif ch == '>':
ang = max(0, ang - 1)
elif ch == '(' and ang == 0:
par_open_idx = idx
break
if par_open_idx == -1:
return ""
# Walk left from par_open_idx to find the start of the name token
j = par_open_idx - 1
# Skip whitespace
while j >= 0 and head[j].isspace():
j -= 1
# Collect identifier (and allow operator forms)
# First, try operator names
m_op = re.search(r'(operator\s*""\s*_[A-Za-z_]\w*|operator\s*[^\s(]+)\s*$', head[:par_open_idx])
if m_op:
name = m_op.group(1)
else:
# Regular identifier (possibly destructor)
m_id = re.search(r'(~?[A-Za-z_]\w*)\s*$', head[:par_open_idx])
name = m_id.group(1) if m_id else ""
if not name or name in self._kw_block:
return ""
return name
def _qualify(self, name: str) -> str:
ns = [p for p in self.ns_stack if p]
q = "::".join(ns) + "::" if ns else ""
if self.class_stack:
q += "::".join([c["name"] for c in self.class_stack]) + "::"
return (q + name) if q else name
def _kind_for_method(self, name: str, cls: str) -> str:
if name == cls: return "ctor"
if name == f"~{cls}": return "dtor"
if name.startswith("operator"):
if re.match(r'operator\s+[^(\s]+', name) and "<" not in name and name != "operator()":
return "conversion"
return "method"
return "method"
def _cvref_static(self, decl: str) -> Tuple[bool,bool,str]:
is_static = bool(re.search(r'(^|\s)static\s', decl))
r = decl.rfind(")")
tail = decl[r+1:] if r!=-1 else ""
is_const = bool(re.search(r'\bconst\b', tail))
refq = "&&" if "&&" in tail else ("&" if re.search(r'(^|\s)&(\s|$)', tail) else "")
return is_static, is_const, refq
def _record_method(self, decl: str, start_line: int, tparams: str):
cls = self.class_stack[-1]["name"]
name = self._name_from_decl(decl)
if not name: return
qualified = self._qualify(name)
is_static, is_const, refq = self._cvref_static(decl)
kind = self._kind_for_method(name, cls)
sig = self._normalize((tparams + " " + decl).strip() if tparams else decl)
self.syms.append(Symbol(kind=kind, qualified=qualified, signature=sig,
file=self.relpath, line=start_line,
static=is_static, const=is_const, ref_qual=refq,
template_params=tparams or ""))
def _record_free_function(self, decl: str, start_line: int, tparams: str):
name = self._name_from_decl(decl)
if not name: return
qualified = self._qualify(name)
sig = self._normalize((tparams + " " + decl).strip() if tparams else decl)
self.syms.append(Symbol(kind="free_function", qualified=qualified, signature=sig,
file=self.relpath, line=start_line,
template_params=tparams or ""))
# -------- Rendering --------
def to_json(symbols: List[Symbol]) -> str:
items = [asdict(s) for s in symbols]
items.sort(key=lambda s: (s["file"], s["line"], s["qualified"], s["signature"]))
return json.dumps({"version": 1, "symbols": items}, indent=2)
def _markdown_for_file(rel_repo_file: str, symbols: List[Symbol]) -> str:
"""
Build per-header Markdown for exactly the symbols whose s.file == rel_repo_file.
"""
title = rel_repo_file.replace("src/", "", 1)
lines = [f"# {title}\n"]
file_syms = [s for s in symbols if s.file == rel_repo_file]
if not file_syms:
lines.append("_No public API symbols found in this header._")
lines.append("")
return "\n".join(l.rstrip() for l in lines)
# Group macros last; keep deterministic order
def _order(s: Symbol):
k = {"macro": 2}.get(s.kind, 1)
return (k, s.qualified, s.signature)
for s in sorted(file_syms, key=_order):
tprefix = (s.template_params + " ") if s.template_params else ""
if s.kind == "macro":
# H2 with macro name, then macro head; no line numbers, no bullets
lines.append(f"## `{s.qualified}`")
lines.append(f"`{s.signature}`\n")
else:
# H2 with fully qualified name (namespace::[class::]func)
# Contract/signature on the next line
fqname = s.qualified
if tprefix:
lines.append(f"## `{fqname}`")
lines.append(f"`{tprefix.strip()} {s.signature}`\n".replace(" ", " ").strip())
else:
lines.append(f"## `{fqname}`")
lines.append(f"`{s.signature}`\n")
return "\n".join(l.rstrip() for l in lines)
# -------- Robust multi-line free-function extraction --------
# Matches things like:
# inline void foo(A a,
# B b = std::nullopt) noexcept;
# std::mutex &error_mutex() noexcept;
_FREE_FN_RE = re.compile(r"""
(?P<prefix> ^ | [;\}\n] ) # anchor
(?P<head>
(?:\s*(?:inline|constexpr|consteval|constinit|static|extern)\s+)* # storage/attrs
(?:[\w:\<\>\*\&\s]+\s+)? # return type (optional for constructors, but we only accept when present)
(?P<name>[A-Za-z_]\w*)\s* # function name
\(
(?P<params>
[^()]* (?:\([^()]*\)[^()]*)* # balanced parens inside params
)
\)
(?:\s*noexcept(?:\s*\([^)]*\))?)? # optional noexcept/noexcept(expr)
(?:\s*->\s*[^;{\n]+)? # optional trailing return type
)
\s*
(?P<ender> [;{] ) # prototype or definition
""", re.VERBOSE | re.DOTALL | re.MULTILINE)
def _collapse_ws(s: str) -> str:
# Collapse all whitespace runs to a single space for clean signatures
return " ".join(s.split())
def extract_free_functions_multiline(clean_text: str, relpath: str) -> List[Symbol]:
"""
Walk the file tracking namespace blocks and pick out free-function
heads that can span multiple lines. Avoid class/struct/enum bodies.
"""
syms: List[Symbol] = []
ns_stack: List[str] = []
class_depth = 0 # crude guard: skip when inside class/struct/enum body
# Token-ish scan to maintain simple block context
i = 0
n = len(clean_text)
while i < n:
# namespace enter
if clean_text.startswith("namespace", i):
j = i + len("namespace")
while j < n and clean_text[j].isspace():
j += 1
# Parse namespace name (could be 'dofs' or anonymous)
k = j
while k < n and (clean_text[k].isalnum() or clean_text[k] in "_:"):
k += 1
ns_name = clean_text[j:k].strip()
# Find the next '{'
m = clean_text.find("{", k)
if m != -1:
if ns_name:
ns_stack.append(ns_name)
else:
ns_stack.append("") # anonymous
i = m + 1
continue
# class/struct/enum guard
if clean_text.startswith("class ", i) or clean_text.startswith("struct ", i) or clean_text.startswith("enum ", i):
# Enter body at next '{'
m = clean_text.find("{", i)
if m != -1:
class_depth += 1
i = m + 1
continue
if clean_text[i] == '}':
if class_depth > 0:
class_depth -= 1
elif ns_stack:
ns_stack.pop()
i += 1
continue continue
scrubbed.append(line)
text = "\n".join(scrubbed) # Try a function head only if not inside a class-like body
if class_depth == 0:
m = _FREE_FN_RE.match(clean_text, i)
if m:
name = m.group("name")
head = m.group("head")
# filter obvious false positives: require a return type before name
# (very rough: there must be at least one space before name inside head)
if re.search(r"\S\s+" + re.escape(name) + r"\s*\(", head):
qualified = "::".join([ns for ns in ns_stack if ns]) # drop anonymous
qualified = f"{qualified}::{name}" if qualified else name
# Build a tidy signature
ender = m.group("ender")
signature = _collapse_ws(head) + ender
line = clean_text.count("\n", 0, m.start("head")) + 1
syms.append(Symbol(kind="free_function",
qualified=qualified,
signature=signature,
file=relpath,
line=line))
i = m.end()
continue
out = [] i += 1
for raw in text.splitlines(): return syms
line = raw.strip()
if not line or line.startswith(SKIP_PREFIXES):
continue
if POSSIBLE_FUNC_DECL_RE.match(line):
out.append(collapse_ws(line))
elif POSSIBLE_INLINE_DEF_RE.match(line):
sig = line.split("{", 1)[0].rstrip()
out.append(collapse_ws(sig) + " { ... }")
return out
def split_top_level_classes(code: str): # -------- Macro extraction (function-like only) --------
lines = code.splitlines() _MACRO_HEAD_RE = re.compile(r'^\s*#\s*define\s+([A-Za-z_]\w*)\s*\((.*)$')
results = []
def extract_function_like_macros(text: str, relpath: str) -> List[Symbol]:
"""
Capture lines of the form:
#define NAME(args) <body...>
with multi-line bodies using backslash continuations.
We record: kind="macro", qualified=NAME, signature="#define NAME(args)".
"""
syms: List[Symbol] = []
lines = text.splitlines()
i = 0 i = 0
while i < len(lines): while i < len(lines):
m = CLASS_START_RE.match(lines[i]) line = lines[i]
m = _MACRO_HEAD_RE.match(line)
if not m: if not m:
i += 1 i += 1
continue continue
kind, name = m.group(1), m.group(2) name = m.group(1)
# Find opening brace on same or subsequent lines args_part = m.group(2) # may or may not contain closing ')'
j = i start_line = i + 1
if "{" not in lines[j]: # Collect continuation lines while trailing backslash exists.
j += 1 body_lines = [line]
while j < len(lines) and "{" not in lines[j]: i += 1
j += 1 while i < len(lines) and body_lines[-1].rstrip().endswith("\\"):
if j >= len(lines): body_lines.append(lines[i])
i += 1 i += 1
continue # Reconstruct just the macro head (name + (...) args text).
# Capture until matching close head = "".join(body_lines)
depth = 0 # Try to extract the argument list reliably (balanced parens from first '(')
body = [] # without being confused by body parentheses.
while j < len(lines): head_from_paren = head[head.find("("):] if "(" in head else ""
depth += lines[j].count("{") # Minimal balanced scan to the first matching ')'
depth -= lines[j].count("}") par = 0
body.append(lines[j]) arg_end = -1
if depth <= 0 and "}" in lines[j]: for idx, ch in enumerate(head_from_paren):
break if ch == "(":
j += 1 par += 1
body_inner = body[1:-1] if body else [] elif ch == ")":
results.append((name, kind == "struct", body_inner)) par -= 1
i = j + 1 if par == 0:
return results arg_end = idx
break
# ---------- Main per-file processing ---------- if arg_end != -1:
arg_text = head_from_paren[1:arg_end] # inside (...)
def process_header(path: Path): else:
raw = read_text(path) # Fallback: whatever we saw on the first line
if not raw: arg_text = args_part.split(")")[0]
return None signature = f"#define {name}({arg_text.strip()})"
syms.append(Symbol(kind="macro",
code = strip_comments(raw) qualified=name,
signature=signature,
# Collect classes file=relpath,
class_entries = [] line=start_line))
for cname, is_struct, body in split_top_level_classes(code): return syms
methods = extract_public_methods(body, is_struct_default_public=is_struct)
if methods:
class_entries.append((cname, methods))
# Collect free function decls
free_funcs = extract_free_function_decls(code)
if not class_entries and not free_funcs:
return None
# Build markdown with ### `signature` items
rel = path.relative_to(SRC_DIR)
md_lines = []
md_lines.append(f"# {rel.as_posix()}")
md_lines.append("")
if free_funcs:
md_lines.append("## Free functions")
md_lines.append("")
for sig in free_funcs:
md_lines.append(f"### `{sig}`")
md_lines.append("")
for cname, methods in class_entries:
md_lines.append(f"## class {cname} — public interface")
md_lines.append("")
for sig in methods:
md_lines.append(f"### `{sig}`")
md_lines.append("")
return "\n".join(md_lines)
def write_markdown(src_header: Path, content: str):
rel = src_header.relative_to(SRC_DIR)
out_path = DOCS_DIR / rel
out_path = out_path.with_suffix(".md")
ensure_dir(out_path)
out_path.write_text(content, encoding="utf-8")
return out_path
# -------- Driver --------
def main(): def main():
if not SRC_DIR.exists(): ap = argparse.ArgumentParser(description="Extract DOFS public API (per-header docs).")
print(f"[ERR] src/ not found at {SRC_DIR}", file=sys.stderr) ap.add_argument("--src", default=str(SRC_ROOT), help="Source root (default: repo/src)")
sys.exit(1) ap.add_argument("--out-dir", default=str(OUT_DIR_DEFAULT), help="Docs root to mirror into (default: docs)")
ap.add_argument("--stdout", action="store_true", help="Print JSON to stdout instead of writing files")
args = ap.parse_args()
generated = 0 src_root = Path(args.src).resolve()
for path in SRC_DIR.rglob("*"): out_root = Path(args.out_dir).resolve()
if not path.is_file():
continue
if path.suffix.lower() not in HEADER_EXTS:
continue
result = process_header(path)
if result:
out = write_markdown(path, result)
generated += 1
print(f"[OK] {out.relative_to(REPO_ROOT)}")
if generated == 0: all_symbols: List[Symbol] = []
print("[INFO] no public interfaces detected (heuristics may have filtered everything)]") header_paths = iter_headers(src_root)
for hp in header_paths:
rel_repo = hp.relative_to(REPO_ROOT).as_posix() # e.g., src/core/simulator.h
raw = read_text(hp)
clean = strip_comments_and_literals(raw)
p = Parser(clean, rel_repo)
# C++ functions/methods (public) inside namespace dofs
parsed = p.run()
all_symbols.extend(parsed)
# Multi-line free functions (e.g., log_error in error.h)
extra_fns = extract_free_functions_multiline(clean, rel_repo)
# De-duplicate by (kind, qualified, signature, file, line)
seen = { (s.kind, s.qualified, s.signature, s.file, s.line) for s in all_symbols }
for s in extra_fns:
key = (s.kind, s.qualified, s.signature, s.file, s.line)
if key not in seen:
all_symbols.append(s)
seen.add(key)
# Function-like macros (global, regardless of namespace)
all_symbols.extend(extract_function_like_macros(raw, rel_repo))
if args.stdout:
print(to_json(all_symbols))
return
# Write index.json under docs/
out_root.mkdir(parents=True, exist_ok=True)
(out_root / "index.json").write_text(to_json(all_symbols), encoding="utf-8")
# Emit one markdown per header, mirroring src/ -> docs/
# src/<subpath>.h => docs/<subpath>.md
for hp in header_paths:
rel_from_repo = hp.relative_to(REPO_ROOT).as_posix() # src/...
rel_from_src = hp.relative_to(src_root).with_suffix(".md") # core/simulator.md
target_path = out_root / rel_from_src
target_path.parent.mkdir(parents=True, exist_ok=True)
md = _markdown_for_file(rel_from_repo, all_symbols)
target_path.write_text(md, encoding="utf-8")
print(f"[extract_api] Wrote JSON index: {out_root/'index.json'}")
print(f"[extract_api] Wrote per-header Markdown under: {out_root}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()