From 3411089908f7ac667ff5587c08ac804da50e4a33 Mon Sep 17 00:00:00 2001 From: Peisong Xiao Date: Sun, 7 Sep 2025 11:47:15 -0400 Subject: [PATCH] added networking components, hosts are next --- .dir-locals.el | 31 ++ .gitignore | 2 + .gitmodules | 3 + CMakeLists.txt | 38 ++ src/core/CMakeLists.txt | 56 ++ src/core/error.cc | 34 ++ src/core/error.h | 54 ++ src/core/host.cc | 29 ++ src/core/host.h | 45 ++ src/core/logger.cc | 67 +++ src/core/logger.h | 53 ++ src/core/node.cc | 67 +++ src/core/node.h | 39 ++ src/core/rng.h | 131 +++++ src/core/simulator.cc | 157 ++++++ src/core/simulator.h | 120 +++++ src/core/time.h | 129 +++++ src/core/timer.h | 56 ++ src/core/types.h | 70 +++ src/hosts/CMakeLists.txt | 49 ++ src/hosts/nodes_dummy.cc | 6 + src/hosts/nodes_dummy.h | 4 + src/network/CMakeLists.txt | 58 +++ src/network/link.cc | 182 +++++++ src/network/link.h | 120 +++++ src/network/network_nic.cc | 660 ++++++++++++++++++++++++ src/network/network_nic.h | 154 ++++++ src/network/network_node.cc | 9 + src/network/network_node.h | 24 + src/network/network_switch.cc | 238 +++++++++ src/network/network_switch.h | 81 +++ src/network/nic/CMakeLists.txt | 50 ++ src/network/nic/congestion_control.cc | 100 ++++ src/network/nic/congestion_control.h | 47 ++ src/network/nic/load_balance.cc | 18 + src/network/nic/load_balance.h | 36 ++ src/network/nic/nic_telemetry.h | 28 + src/network/packet.cc | 180 +++++++ src/network/packet.h | 97 ++++ src/network/switch/CMakeLists.txt | 63 +++ src/network/switch/dedicated_buffer.cc | 65 +++ src/network/switch/dedicated_buffer.h | 38 ++ src/network/switch/ecn_dedicated_red.cc | 50 ++ src/network/switch/ecn_dedicated_red.h | 54 ++ src/network/switch/ecn_engine.h | 51 ++ src/network/switch/ecn_shared_red.cc | 126 +++++ src/network/switch/ecn_shared_red.h | 71 +++ src/network/switch/multicast_table.cc | 208 ++++++++ src/network/switch/multicast_table.h | 52 ++ src/network/switch/routing_alg.cc | 36 ++ src/network/switch/routing_alg.h | 26 + src/network/switch/routing_tables.h | 16 + src/network/switch/shared_buffer.cc | 63 +++ src/network/switch/shared_buffer.h | 39 ++ src/network/switch/switch_buffer.cc | 244 +++++++++ src/network/switch/switch_buffer.h | 162 ++++++ src/network/switch/unicast_table.cc | 54 ++ src/network/switch/unicast_table.h | 36 ++ third_party/yaml-cpp | 1 + 59 files changed, 4777 insertions(+) create mode 100644 .dir-locals.el create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 src/core/CMakeLists.txt create mode 100644 src/core/error.cc create mode 100644 src/core/error.h create mode 100644 src/core/host.cc create mode 100644 src/core/host.h create mode 100644 src/core/logger.cc create mode 100644 src/core/logger.h create mode 100644 src/core/node.cc create mode 100644 src/core/node.h create mode 100644 src/core/rng.h create mode 100644 src/core/simulator.cc create mode 100644 src/core/simulator.h create mode 100644 src/core/time.h create mode 100644 src/core/timer.h create mode 100644 src/core/types.h create mode 100644 src/hosts/CMakeLists.txt create mode 100644 src/hosts/nodes_dummy.cc create mode 100644 src/hosts/nodes_dummy.h create mode 100644 src/network/CMakeLists.txt create mode 100644 src/network/link.cc create mode 100644 src/network/link.h create mode 100644 src/network/network_nic.cc create mode 100644 src/network/network_nic.h create mode 100644 src/network/network_node.cc create mode 100644 src/network/network_node.h create mode 100644 src/network/network_switch.cc create mode 100644 src/network/network_switch.h create mode 100644 src/network/nic/CMakeLists.txt create mode 100644 src/network/nic/congestion_control.cc create mode 100644 src/network/nic/congestion_control.h create mode 100644 src/network/nic/load_balance.cc create mode 100644 src/network/nic/load_balance.h create mode 100644 src/network/nic/nic_telemetry.h create mode 100644 src/network/packet.cc create mode 100644 src/network/packet.h create mode 100644 src/network/switch/CMakeLists.txt create mode 100644 src/network/switch/dedicated_buffer.cc create mode 100644 src/network/switch/dedicated_buffer.h create mode 100644 src/network/switch/ecn_dedicated_red.cc create mode 100644 src/network/switch/ecn_dedicated_red.h create mode 100644 src/network/switch/ecn_engine.h create mode 100644 src/network/switch/ecn_shared_red.cc create mode 100644 src/network/switch/ecn_shared_red.h create mode 100644 src/network/switch/multicast_table.cc create mode 100644 src/network/switch/multicast_table.h create mode 100644 src/network/switch/routing_alg.cc create mode 100644 src/network/switch/routing_alg.h create mode 100644 src/network/switch/routing_tables.h create mode 100644 src/network/switch/shared_buffer.cc create mode 100644 src/network/switch/shared_buffer.h create mode 100644 src/network/switch/switch_buffer.cc create mode 100644 src/network/switch/switch_buffer.h create mode 100644 src/network/switch/unicast_table.cc create mode 100644 src/network/switch/unicast_table.h create mode 160000 third_party/yaml-cpp diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..2f048c3 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,31 @@ +((c++-mode . ((cmake-ide-project-dir . "/home/peisongxiao/Projects/dofs") + (cmake-ide-build-dir . "/home/peisongxiao/Projects/dofs/build/ide") + (cmake-ide-cmake-args . ("-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" + "-DCMAKE_BUILD_TYPE=Debug" + "-DDOFS_GLOB_SOURCES=ON" + "-DYAML_BUILD_SHARED_LIBS=OFF" + "-DYAML_CPP_BUILD_TESTS=OFF" + "-DYAML_CPP_BUILD_TOOLS=OFF" + "-DYAML_ENABLE_PIC=ON")) + ;; KEY BIT: let ff-find-other-file look in build/ide/src/** for stubs (time.h -> build/ide/src/.../time.cpp) + (eval . (let* ((proj (locate-dominating-file default-directory "CMakeLists.txt")) + (build (expand-file-name "build/ide" proj))) + (setq-local ff-search-directories + (list + "." ; current dir + "../src/**" ; sibling source + "/home/peisongxiao/Projects/dofs/build/ide/src/**" + (concat build "/src/**") ; generated stubs live here + "/usr/include" "/usr/local/include")) + ;; prefer “first TU that includes this header” when available + (with-eval-after-load 'cmake-ide + (setq cmake-ide-header-search-first-including t + cmake-ide-header-search-other-file t)) + ;; keep Flycheck on + (flycheck-mode 1) + (company-mode 1)))))) + +((nil . ((cmake-ide-build-dir . "build") + (cmake-ide-project-dir . ".") + (cmake-ide-cmake-opts . "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON") + (cmake-ide-debug . t)))) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc7942d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +prompt.txt diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7378aa9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "third_party/yaml-cpp"] + path = third_party/yaml-cpp + url = https://git.peisongxiao.com/peisongxiao/yaml-cpp.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..dd70782 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.20) +project(dofs LANGUAGES CXX) + +# Global C++ settings +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "Export compile_commands.json") + +# Dev toggle: convenient but less deterministic than listing files explicitly +option(DOFS_GLOB_SOURCES "Dev: auto-add *.cc files via GLOB (not ideal for CI)" OFF) + +# Interface target to export common config (include dirs, features) +add_library(dofs_config INTERFACE) +target_compile_features(dofs_config INTERFACE cxx_std_20) + +# Let everyone include headers like: #include "core/error.h" +target_include_directories(dofs_config INTERFACE "${PROJECT_SOURCE_DIR}/src") + +# yaml-cpp submodule (static) +if(EXISTS "${PROJECT_SOURCE_DIR}/third_party/yaml-cpp/CMakeLists.txt") + # Use yaml-cpp's options; force them at configure-time + set(YAML_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "" FORCE) + set(YAML_CPP_BUILD_CONTRIB ON CACHE BOOL "" FORCE) + set(YAML_ENABLE_PIC ON CACHE BOOL "" FORCE) + set(YAML_CPP_INSTALL OFF CACHE BOOL "" FORCE) + + add_subdirectory(third_party/yaml-cpp EXCLUDE_FROM_ALL) +else() + message(WARNING "yaml-cpp submodule missing at third_party/yaml-cpp") +endif() + +# add_subdirectory(src) +add_subdirectory(src/core) +add_subdirectory(src/network) +add_subdirectory(src/hosts) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt new file mode 100644 index 0000000..94e4dca --- /dev/null +++ b/src/core/CMakeLists.txt @@ -0,0 +1,56 @@ +add_library(dofs_core STATIC) + +# Propagate include dir via dofs_config to anyone linking dofs_core +target_link_libraries(dofs_core PUBLIC dofs_config) + +target_include_directories(dofs_core + PUBLIC + $ + $ +) + +if(TARGET yaml-cpp) + target_link_libraries(dofs_core PUBLIC yaml-cpp) +endif() + +if(DOFS_GLOB_SOURCES) + file(GLOB CORE_CC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cc") + target_sources(dofs_core PRIVATE ${CORE_CC}) +else() + # Prefer explicit lists for reproducibility + target_sources(dofs_core + PUBLIC + time.h + timer.h + types.h + error.h + rng.h + logger.h + simulator.h + node.h + host.h + PRIVATE + error.cc + logger.cc + simulator.cc + node.cc + host.cc + ) +endif() + +add_library(dofs::core ALIAS dofs_core) + +# --- Tooling-only unity TU for headers (core) --- +# Generate per-header stubs with the SAME basename (time.h -> time.cpp) +file(GLOB CORE_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.h") +set(CORE_STUBS) +foreach(h ${CORE_HEADERS}) + get_filename_component(_base "${h}" NAME_WE) + set(_stub "${CMAKE_CURRENT_BINARY_DIR}/${_base}.cpp") + file(WRITE "${_stub}" "// tooling stub for ${_base}.h\n#include \"core/${_base}.h\"\n") + list(APPEND CORE_STUBS "${_stub}") +endforeach() + +add_library(dofs_core_headers_tooling STATIC ${CORE_STUBS}) +target_link_libraries(dofs_core_headers_tooling PRIVATE dofs_core) +# This target exists purely for compile_commands; no need to link it elsewhere. diff --git a/src/core/error.cc b/src/core/error.cc new file mode 100644 index 0000000..432d8fd --- /dev/null +++ b/src/core/error.cc @@ -0,0 +1,34 @@ +#include "error.h" + +#include +#include +#include + +namespace dofs { + +static std::mutex g_error_mutex; + +std::mutex &error_mutex() noexcept { + return g_error_mutex; +} + +static inline void emit_error_line(std::string_view type, + std::optional ts, + std::string_view message) noexcept { + std::lock_guard lock(g_error_mutex); + std::cerr << '[' << type << ']'; + + if (ts.has_value()) { + std::cerr << '[' << *ts << ']'; + } + + std::cerr << ": " << message << '\n'; +} + +void log_error(std::string_view type, + std::string_view message, + std::optional timestamp) noexcept { + emit_error_line(type, timestamp, message); +} + +} // namespace dofs diff --git a/src/core/error.h b/src/core/error.h new file mode 100644 index 0000000..5ab2eef --- /dev/null +++ b/src/core/error.h @@ -0,0 +1,54 @@ +#ifndef CORE_ERROR_H +#define CORE_ERROR_H + +#include +#include +#include +#include +#include +#include +#include + +namespace dofs { + +void log_error(std::string_view type, + std::string_view message, + std::optional timestamp = std::nullopt) noexcept; + +std::mutex &error_mutex() noexcept; + +template +inline T&& show(std::string_view name, T&& value) noexcept { + std::lock_guard lock(error_mutex()); + std::cerr << name << '=' << value << '\n'; + return std::forward(value); +} + +template +inline T&& eval_and_show(std::string_view expr, T&& value) noexcept { + std::lock_guard lock(error_mutex()); + std::cerr << expr << '=' << value << '\n'; + return std::forward(value); +} + +#define DOFS_ERROR(TYPE, MSG) \ + ::dofs::log_error((TYPE), (MSG)) + +#define DOFS_ERROR_T(TYPE, MSG, TS) \ + ::dofs::log_error((TYPE), (MSG), (TS)) + +#define DOFS_ERROR_ST(TYPE, SRC, MSG, TS) \ + ::dofs::log_error((TYPE), (SRC), (MSG), (TS)) + +#define DOFS_SHOW(VAR) \ + (::dofs::show(#VAR, (VAR))) + +#define DOFS_EVAL(EXPR) \ + (::dofs::eval_and_show(#EXPR, (EXPR))) + +#define FORMAT(VAR) \ + (std::format("{}={}", #VAR, VAR)) + +} // namespace dofs + +#endif // CORE_ERROR_H diff --git a/src/core/host.cc b/src/core/host.cc new file mode 100644 index 0000000..28c429d --- /dev/null +++ b/src/core/host.cc @@ -0,0 +1,29 @@ +#include "core/host.h" +#include "core/error.h" + +namespace dofs { + +Host::Host(Simulator *const sim, NodeId id) noexcept + : Node(sim, id, NodeType::HOST), _nic(nullptr) { + // Hosts start in OK unless caller overrides later. + Node::set_status(NodeStatus::OK); +} + +void Host::attach_nic(NetworkNic* nic) noexcept { + if (_nic && _nic != nic) { + log_error("ERROR", "Host::attach_nic called while NIC already attached"); + } + + _nic = nic; +} + +void Host::detach_nic(NetworkNic* nic) noexcept { + if (_nic && _nic != nic) { + log_error("ERROR", "Host::detach_nic called with non-matching NIC pointer"); + return; + } + + _nic = nullptr; +} + +} // namespace dofs diff --git a/src/core/host.h b/src/core/host.h new file mode 100644 index 0000000..107ddfd --- /dev/null +++ b/src/core/host.h @@ -0,0 +1,45 @@ +#ifndef CORE_HOST_H +#define CORE_HOST_H + +#include + +#include "core/node.h" +#include "core/types.h" +#include "network/packet.h" + +namespace dofs { + +class NetworkNic; + +class Host : public Node { +public: + Host(Simulator *const sim, NodeId id) noexcept; + virtual ~Host() = default; + + // Access to the attached NIC (may be null if detached) + NetworkNic *nic() const noexcept { + return _nic; + } + + // NIC lifecycle hooks (called by the NIC) + void attach_nic(NetworkNic* nic) noexcept; + void detach_nic(NetworkNic* nic) noexcept; + + // Data-plane completion: a whole flow has arrived. + virtual void recv_flow(NodeId src, + FlowPriority priority, + Bytes flow_size) = 0; + + // Control/telemetry interrupt: ACK/NACK/TRIM_BACK, etc. + virtual void recv_frame(const Packet& frame) = 0; + + Host(const Host &) = delete; + Host &operator=(const Host &) = delete; + +private: + NetworkNic *_nic{nullptr}; +}; + +} // namespace dofs + +#endif // CORE_HOST_H diff --git a/src/core/logger.cc b/src/core/logger.cc new file mode 100644 index 0000000..76471b7 --- /dev/null +++ b/src/core/logger.cc @@ -0,0 +1,67 @@ +#include "logger.h" + +#include +#include + +namespace dofs { + +Logger::Logger(std::string_view path, bool append) noexcept + : _path(path) { + const std::ios::openmode mode = append ? std::ios::app : std::ios::trunc; + (void)open(mode); +} + +bool Logger::open(std::ios::openmode mode) noexcept { + _stream.exceptions(std::ios::goodbit); + _stream.open(_path, std::ios::out | mode); + _is_open = _stream.is_open(); + + if (!_is_open) { + DOFS_ERROR("Logger", ("failed to open file: " + _path).c_str()); + } + + return _is_open; +} + +Logger::~Logger() noexcept { + close(); +} + +void Logger::flush() noexcept { + std::lock_guard _lk(_mutex); + + if (_is_open) { + _stream.flush(); + + if (!_stream) { + DOFS_ERROR("Logger", ("flush failed for: " + _path).c_str()); + } + } +} + +void Logger::close() noexcept { + std::lock_guard _lk(_mutex); + + if (_is_open) { + _stream.flush(); + _stream.close(); + _is_open = false; + } +} + +void Logger::write_line(std::string_view line) noexcept { + std::lock_guard _lk(_mutex); + + if (!_is_open) { + DOFS_ERROR("Logger", ("write on closed file: " + _path).c_str()); + return; + } + + _stream << line << '\n'; + + if (!_stream) { + DOFS_ERROR("Logger", ("write failed for: " + _path).c_str()); + } +} + +} // namespace dofs diff --git a/src/core/logger.h b/src/core/logger.h new file mode 100644 index 0000000..fefdbfc --- /dev/null +++ b/src/core/logger.h @@ -0,0 +1,53 @@ +#ifndef CORE_LOGGER_H +#define CORE_LOGGER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/error.h" +#include "core/types.h" + +namespace dofs { + +class Logger final { +public: + Logger(std::string_view path, bool append) noexcept; + ~Logger() noexcept; + + Logger(const Logger &) = delete; + Logger &operator=(const Logger &) = delete; + Logger(Logger &&) = delete; + Logger &operator=(Logger &&) = delete; + + bool is_open() const noexcept { + return _is_open; + } + + void write_line(std::string_view line) noexcept; + + void flush() noexcept; + void close() noexcept; + + std::string_view path() const noexcept { + return _path; + } + +private: + bool open(std::ios::openmode mode) noexcept; + + std::string _path; + std::ofstream _stream; + bool _is_open{false}; + std::mutex _mutex; +}; + +} // namespace dofs + +#endif // CORE_LOGGER_H diff --git a/src/core/node.cc b/src/core/node.cc new file mode 100644 index 0000000..0758335 --- /dev/null +++ b/src/core/node.cc @@ -0,0 +1,67 @@ +#include "core/node.h" + +#include "core/simulator.h" +#include "core/error.h" + +#include + +namespace dofs { + +Node::Node(Simulator *const sim, NodeId id, NodeType type) noexcept + : _sim(sim), _id(id), _status(NodeStatus::OK), _type(type) {} + +// Accessors +NodeId Node::id() const noexcept { + return _id; +} +NodeStatus Node::status() const noexcept { + return _status; +} +NodeType Node::type() const noexcept { + return _type; +} + +void Node::set_status(NodeStatus s) noexcept { + _status = s; +} + +static inline bool schedule_status_ok_after(Simulator *const sim, Time delay_ns, + Node* self) { + if (!sim) { + log_error("ERROR", "Node: simulator instance not found for boot/reboot"); + return false; + } + + sim->schedule_after(delay_ns, [self]() { + self->set_status(NodeStatus::OK); + }); + return true; +} + +void Node::boot(Time boottime_ns) { + if (_status != NodeStatus::DOWN) { + log_error("ERROR", "boot() ignored: node not in DOWN state"); + return; + } + + _status = NodeStatus::BOOTING; + + if (!schedule_status_ok_after(_sim, boottime_ns, this)) { + // Intentionally left BOOTING; caller can handle recovery/logs. + } +} + +void Node::reboot(Time boottime_ns) { + if (_status != NodeStatus::OK && _status != NodeStatus::THROTTLING) { + log_error("ERROR", "reboot() ignored: node not in OK/THROTTLING state"); + return; + } + + _status = NodeStatus::BOOTING; + + if (!schedule_status_ok_after(_sim, boottime_ns, this)) { + // Intentionally left BOOTING; caller can handle recovery/logs. + } +} + +} // namespace dofs diff --git a/src/core/node.h b/src/core/node.h new file mode 100644 index 0000000..a89875b --- /dev/null +++ b/src/core/node.h @@ -0,0 +1,39 @@ +#ifndef CORE_NODE_H +#define CORE_NODE_H + +#include + +#include "core/simulator.h" +#include "core/time.h" +#include "core/types.h" + +namespace dofs { + +class Node { +public: + Node(Simulator *const sim, NodeId id, NodeType type) noexcept; + virtual ~Node() = default; + + NodeId id() const noexcept; + NodeStatus status() const noexcept; + NodeType type() const noexcept; + + void set_status(NodeStatus s) noexcept; + + void boot(Time boottime_ns); + void reboot(Time boottime_ns); + + Node(const Node &) = delete; + Node &operator=(const Node &) = delete; + +protected: + Simulator *const _sim; + + NodeId _id; + NodeStatus _status; + NodeType _type; +}; + +} // namespace dofs + +#endif // CORE_NODE_H diff --git a/src/core/rng.h b/src/core/rng.h new file mode 100644 index 0000000..59c6c36 --- /dev/null +++ b/src/core/rng.h @@ -0,0 +1,131 @@ +#ifndef CORE_RNG_H +#define CORE_RNG_H + +#include +#include +#include +#include +#include +#include +#include + +namespace dofs { + +class Rng final { +public: + using engine_type = std::mt19937_64; + using seed_type = engine_type::result_type; + + explicit Rng(seed_type seed = default_seed()) noexcept + : _eng(seed) {} + + void seed(seed_type s) noexcept { + _eng.seed(s); + } + + // Real in [0, 1) + double uniform01() { + return std::uniform_real_distribution {}(_eng); + } + + // Integer in [lo, hi) — lo inclusive, hi exclusive + template ::value>> + Int uniform_range(Int lo_inclusive, Int hi_exclusive) { + return std::uniform_int_distribution(lo_inclusive, hi_exclusive - 1)(_eng); + } + + // Integer in [0, hi) — convenience overload + template ::value>> + Int uniform_range(Int hi_exclusive) { + return uniform_range(0, hi_exclusive); + } + + double uniform_range(double lo_inclusive, double hi_exclusive) { + + return lo_inclusive + (hi_exclusive - lo_inclusive) * uniform01(); + } + + std::uint64_t poisson(double lambda) { + return static_cast(std::poisson_distribution + (lambda)(_eng)); + } + + template + T choose_weighted(const std::vector>& items) { + return choose_weighted_impl(items.begin(), items.end()); + } + + template + T choose_weighted(std::initializer_list> items) { + return choose_weighted_impl(items.begin(), items.end()); + } + +private: + engine_type _eng; + + static constexpr seed_type default_seed() noexcept { + return 0xC0FFEEULL ^ 0x9E3779B97F4A7C15ULL; + } + + template + auto choose_weighted_impl(Iter first, + Iter last) -> typename std::iterator_traits::value_type::second_type { + using Pair = typename std::iterator_traits::value_type; + using T = typename Pair::second_type; + + double total = 0.0; + + for (auto it = first; it != last; ++it) { + const double w = it->first; + + if (w > 0.0 && std::isfinite(w)) + total += w; + } + + if (!(total > 0.0)) { + std::size_t n = 0; + + for (auto it = first; it != last; ++it) + ++n; + + if (n == 0) + return T{}; + + std::size_t idx = static_cast(uniform_range(0, + static_cast(n))); + + auto it = first; + + while (idx--) + ++it; + + return it->second; + } + + double u = uniform01() * total; + + for (auto it = first; it != last; ++it) { + const double w = (it->first > 0.0 && + std::isfinite(it->first)) ? it->first : 0.0; + + if (u < w) + return it->second; + + u -= w; + } + + auto it = last; + + do { + --it; + } while (it->first <= 0.0 || !std::isfinite(it->first)); + + return it->second; + } +}; + +} // namespace dofs + +#endif // CORE_RNG_H diff --git a/src/core/simulator.cc b/src/core/simulator.cc new file mode 100644 index 0000000..61aef69 --- /dev/null +++ b/src/core/simulator.cc @@ -0,0 +1,157 @@ +#include "core/simulator.h" + +#include + +#include "core/rng.h" +#include "network/link.h" + +namespace dofs { + +bool Simulator::Cmp::operator()(const Item& a, const Item& b) const noexcept { + if (a.when != b.when) + return a.when > b.when; + + return a.id > b.id; +} + +Time Simulator::now() const noexcept { + return _now; +} + +bool Simulator::cancel(EventId id) { + return _cancelled.insert(id).second; +} + +bool Simulator::run_next() { + while (!_event_pq.empty()) { + Item it = _event_pq.top(); + _event_pq.pop(); + + if (_cancelled.erase(it.id) > 0) + continue; + + _now = it.when; + it.fn(); + return true; + } + + return false; +} + +void Simulator::run_until(Time end_time) { + while (!_event_pq.empty()) { + const Item& top = _event_pq.top(); + + if (end_time < top.when) + break; + + run_next(); + } +} + +std::vector>& Simulator::registry_() { + static std::vector> reg; + return reg; +} + +std::pair Simulator::create_simulator(InstanceId id) { + if (id == INVALID_INSTANCE_ID) { + return {INVALID_INSTANCE_ID, nullptr}; + } + + auto& reg = registry_(); + + if (static_cast(id) >= reg.size()) { + reg.resize(static_cast(id) + 1); + } + + if (!reg[static_cast(id)]) { + auto sim = std::make_unique(); + Simulator* ptr = sim.get(); + reg[static_cast(id)] = std::move(sim); + return {id, ptr}; + } + + return {id, reg[static_cast(id)].get()}; +} + +Simulator * Simulator::get_simulator(InstanceId id) noexcept { + if (id == INVALID_INSTANCE_ID) + return nullptr; + + auto& reg = registry_(); + const size_t idx = static_cast(id); + + if (idx >= reg.size()) + return nullptr; + + return reg[idx].get(); +} + +Rng * Simulator::create_rng(std::uint64_t seed) { + if (_rng) + return nullptr; + + _rng = std::make_unique(seed); + return _rng.get(); +} +Rng * Simulator::get_rng() noexcept { + return _rng.get(); +} + +Rng const * Simulator::get_rng() const noexcept { + return _rng.get(); +} + +std::pair Simulator::create_link( + NetworkNode* a, PortId a_port, + NetworkNode* b, PortId b_port, + Time latency, + double bandwidth_gbps) { + + if (!a || !b) + return {INVALID_LINK_ID, nullptr}; + + if (bandwidth_gbps <= 0.0) + return {INVALID_LINK_ID, nullptr}; + + if (latency < Time{0}) + return {INVALID_LINK_ID, nullptr}; + + const LinkId id = static_cast(_links.size()); + auto up = + std::unique_ptr(new Link(this, id, + a, a_port, b, b_port, + latency, + bandwidth_gbps)); + + Link* raw = up.get(); + _links.push_back(std::move(up)); + return {id, raw}; +} + +Link * Simulator::get_link(LinkId id) noexcept { + if (id == INVALID_LINK_ID) + return nullptr; + + const size_t idx = static_cast(id); + + if (idx >= _links.size()) + return nullptr; + + return _links[idx].get(); +} + +Link const * Simulator::get_link(LinkId id) const noexcept { + if (id == INVALID_LINK_ID) + return nullptr; + + const size_t idx = static_cast(id); + + if (idx >= _links.size()) + return nullptr; + + return _links[idx].get(); +} + +} // namespace dofs diff --git a/src/core/simulator.h b/src/core/simulator.h new file mode 100644 index 0000000..5fc45a8 --- /dev/null +++ b/src/core/simulator.h @@ -0,0 +1,120 @@ +#ifndef CORE_SIMULATOR_H +#define CORE_SIMULATOR_H + +// TODO: implement concrete node methods for different nodes, store them all uniformly in vector> and return a get function + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/time.h" +#include "core/types.h" + +namespace dofs { + +class Rng; +class Logger; + +class Node; +class NetworkNode; +class Link; + +constexpr EventId NULL_EVENT = 0; + +constexpr InstanceId INVALID_INSTANCE_ID = + std::numeric_limits::max(); +constexpr LinkId INVALID_LINK_ID = + std::numeric_limits::max(); + +class Simulator final { +private: + struct Item { + Time when; + EventId id; + std::function fn; + }; + + struct Cmp { + bool operator()(const Item& a, const Item& b) const noexcept; + }; + + std::priority_queue, Cmp> _event_pq; + std::unordered_set _cancelled; + Time _now{}; + EventId _next_id{1}; + + std::unique_ptr _rng; + + std::vector> _nodes; + std::vector> _links; + +public: + Simulator() = default; + + static std::pair create_simulator(InstanceId id); + static Simulator *get_simulator(InstanceId id) noexcept; + + Time now() const noexcept; + + template + EventId schedule_at(Time abs_time, F&& f, Args&&... args) { + if (abs_time < _now) + return NULL_EVENT; + + const EventId eid = _next_id++; + Item it{ + abs_time, + eid, + make_callable(std::forward(f), std::forward(args)...) + }; + _event_pq.push(std::move(it)); + return eid; + } + + template + EventId schedule_after(Time delay, F&& f, Args&&... args) { + return schedule_at(_now + delay, std::forward(f), + std::forward(args)...); + } + + bool cancel(EventId id); + bool run_next(); + void run_until(Time end_time); + + // ---------- Object management ---------- + Rng* create_rng(std::uint64_t seed); + Rng* get_rng() noexcept; + Rng const* get_rng() const noexcept; + + std::pair create_link(NetworkNode* a, PortId a_port, + NetworkNode* b, PortId b_port, + Time latency, + double bandwidth_gbps); + + Link* get_link(LinkId id) noexcept; + Link const* get_link(LinkId id) const noexcept; + +private: + template + static auto make_callable(F&& f, Args&&... args) { + using Fn = std::decay_t; + using Tup = std::tuple...>; + return [fn = Fn(std::forward(f)), + tup = Tup(std::forward(args)...)]() mutable { + std::apply(fn, std::move(tup)); + }; + } + + static std::vector>& registry_(); +}; + +} // namespace dofs + +#endif // CORE_SIMULATOR_H diff --git a/src/core/time.h b/src/core/time.h new file mode 100644 index 0000000..ed58636 --- /dev/null +++ b/src/core/time.h @@ -0,0 +1,129 @@ +#ifndef CORE_TIME_H +#define CORE_TIME_H + +#include +#include + +namespace dofs { + +class Time final { +public: + using rep = uint64_t; + + // ----- Constructors ----- + constexpr Time() : _nsec(0) {} + explicit constexpr Time(rep ns) : _nsec(ns) {} + + // ----- Factories ----- + static constexpr Time from_ns(rep ns) noexcept { + return Time(ns); + } + static constexpr Time from_us(rep us) noexcept { + return Time(us * 1000ULL); + } + static constexpr Time from_ms(rep ms) noexcept { + return Time(ms * 1000ULL * 1000ULL); + } + static constexpr Time from_s (rep s ) noexcept { + return Time(s * 1000ULL * 1000ULL * 1000ULL); + } + + // ----- Accessors ----- + constexpr rep ns() const noexcept { + return _nsec; + } + constexpr rep count() const noexcept { + return _nsec; + } + + // ----- Converters (plain uint64_t) ----- + static constexpr rep us_to_ns(rep us) noexcept { + return us * 1000ULL; + } + static constexpr rep ms_to_ns(rep ms) noexcept { + return ms * 1000ULL * 1000ULL; + } + + // ----- Comparisons ----- + friend constexpr bool operator==(Time a, Time b) noexcept { + return a._nsec == b._nsec; + } + + friend constexpr bool operator!=(Time a, Time b) noexcept { + return !(a == b); + } + + friend constexpr bool operator< (Time a, Time b) noexcept { + return a._nsec < b._nsec; + } + + friend constexpr bool operator> (Time a, Time b) noexcept { + return b < a; + } + + friend constexpr bool operator<=(Time a, Time b) noexcept { + return !(b < a); + } + + friend constexpr bool operator>=(Time a, Time b) noexcept { + return !(a < b); + } + + // ----- Arithmetic ----- + constexpr Time &operator+=(Time rhs) noexcept { + _nsec += rhs._nsec; + return *this; + } + constexpr Time &operator-=(Time rhs) noexcept { + if (_nsec < rhs._nsec) + _nsec = 0; + else + _nsec -= rhs._nsec; + + return *this; + } + + friend constexpr Time operator+(Time a, Time b) noexcept { + return Time(a._nsec + b._nsec); + } + + friend constexpr Time operator*(Time a, Time b) noexcept { + return Time(a._nsec * b._nsec); + } + + friend constexpr std::optional