aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-tidy2
-rw-r--r--arch/x86_64/CMakeLists.txt7
-rw-r--r--arch/x86_64/include/arch/bus/isa.hpp18
-rw-r--r--arch/x86_64/include/arch/device_io/port_io.hpp5
-rw-r--r--arch/x86_64/include/arch/devices/legacy_pit.hpp29
-rw-r--r--arch/x86_64/kapi/devices.cpp35
-rw-r--r--arch/x86_64/src/bus/isa.cpp14
-rw-r--r--arch/x86_64/src/cpu/initialization.cpp2
-rw-r--r--arch/x86_64/src/devices/legacy_pit.cpp60
-rw-r--r--kapi/include/kapi/devices.hpp36
-rw-r--r--kapi/include/kapi/devices/bus.hpp95
-rw-r--r--kapi/include/kapi/devices/manager.hpp53
-rw-r--r--kernel/CMakeLists.txt2
-rw-r--r--kernel/include/kernel/devices/root_bus.hpp16
-rw-r--r--kernel/kapi/devices.cpp87
-rw-r--r--kernel/src/devices/root_bus.cpp12
-rw-r--r--kernel/src/main.cpp11
-rw-r--r--libs/kstd/CMakeLists.txt1
-rw-r--r--libs/kstd/include/kstd/bits/flat_map.hpp2
-rw-r--r--libs/kstd/include/kstd/bits/observer_ptr.hpp163
-rw-r--r--libs/kstd/include/kstd/bits/unique_ptr.hpp25
-rw-r--r--libs/kstd/include/kstd/memory9
-rw-r--r--libs/kstd/include/kstd/string13
-rw-r--r--libs/kstd/tests/include/kstd/tests/test_types.hpp71
-rw-r--r--libs/kstd/tests/src/observer_ptr.cpp359
-rw-r--r--libs/kstd/tests/src/vector.cpp119
26 files changed, 1165 insertions, 81 deletions
diff --git a/.clang-tidy b/.clang-tidy
index 8fa3943..61ae9c9 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -60,7 +60,7 @@ CheckOptions:
modernize-use-std-print.ReplacementPrintlnFunction: 'kstd::println'
modernize-use-std-print.PrintHeader: 'kstd/print'
modernize-use-trailing-return-type.TransformLambdas: none
- readability-magic-numbers.IgnoredIntegerValues: '1;2;3;4;5;6;7;10'
+ readability-magic-numbers.IgnoredIntegerValues: '1;2;3;4;5;6;7;10;255'
readability-magic-numbers.IgnorePowersOf2IntegerValues: true
readability-magic-numbers.IgnoreBitFieldsWidths: true
readability-magic-numbers.IgnoreTypeAliases: true
diff --git a/arch/x86_64/CMakeLists.txt b/arch/x86_64/CMakeLists.txt
index 4427e4c..83cae0b 100644
--- a/arch/x86_64/CMakeLists.txt
+++ b/arch/x86_64/CMakeLists.txt
@@ -15,6 +15,7 @@ target_sources("x86_64" PRIVATE
"kapi/boot_modules.cpp"
"kapi/cio.cpp"
"kapi/cpu.cpp"
+ "kapi/devices.cpp"
"kapi/interrupts.cpp"
"kapi/memory.cpp"
"kapi/system.cpp"
@@ -24,6 +25,9 @@ target_sources("x86_64" PRIVATE
"src/cpu/interrupts.cpp"
"src/cpu/interrupt_stubs.S"
+ # Bus Initialization
+ "src/bus/isa.cpp"
+
# Low-level bootstrap
"src/boot/boot32.S"
"src/boot/entry64.s"
@@ -33,6 +37,9 @@ target_sources("x86_64" PRIVATE
# Debug interfaces
"src/debug/qemu_output.cpp"
+ # Devices
+ "src/devices/legacy_pit.cpp"
+
# Memory management
"src/memory/kernel_mapper.cpp"
"src/memory/higher_half_mapper.cpp"
diff --git a/arch/x86_64/include/arch/bus/isa.hpp b/arch/x86_64/include/arch/bus/isa.hpp
new file mode 100644
index 0000000..5deed25
--- /dev/null
+++ b/arch/x86_64/include/arch/bus/isa.hpp
@@ -0,0 +1,18 @@
+#ifndef TEACHOS_X86_64_BUS_ISA_HPP
+#define TEACHOS_X86_64_BUS_ISA_HPP
+
+#include "kapi/devices/bus.hpp"
+
+#include <cstddef>
+
+namespace arch::bus
+{
+
+ struct isa final : public kapi::devices::bus
+ {
+ isa(std::size_t major);
+ };
+
+} // namespace arch::bus
+
+#endif // TEACHOS_X86_64_BUS_ISA_HPP
diff --git a/arch/x86_64/include/arch/device_io/port_io.hpp b/arch/x86_64/include/arch/device_io/port_io.hpp
index 70773dd..4c8d66a 100644
--- a/arch/x86_64/include/arch/device_io/port_io.hpp
+++ b/arch/x86_64/include/arch/device_io/port_io.hpp
@@ -102,6 +102,11 @@ namespace arch::io
: std::string_view{"eax"};
};
+ auto inline wait() -> void
+ {
+ port<0x80, std::uint8_t, port_write>::write<std::uint8_t>(0);
+ }
+
} // namespace arch::io
#endif \ No newline at end of file
diff --git a/arch/x86_64/include/arch/devices/legacy_pit.hpp b/arch/x86_64/include/arch/devices/legacy_pit.hpp
new file mode 100644
index 0000000..de742ae
--- /dev/null
+++ b/arch/x86_64/include/arch/devices/legacy_pit.hpp
@@ -0,0 +1,29 @@
+#ifndef TEACHOS_ARCH_X86_64_DEVICES_LEGACY_PIT_HPP
+#define TEACHOS_ARCH_X86_64_DEVICES_LEGACY_PIT_HPP
+
+#include "kapi/devices/device.hpp"
+#include "kapi/interrupts.hpp"
+
+#include <cstddef>
+#include <cstdint>
+
+namespace arch::devices
+{
+
+ struct legacy_pit : kapi::devices::device, kapi::interrupts::handler
+ {
+ legacy_pit(std::size_t major, std::uint32_t frequency_in_hz);
+
+ auto init() -> bool override;
+
+ auto handle_interrupt(std::uint32_t irq_number) -> kapi::interrupts::status override;
+
+ private:
+ std::uint32_t m_irq_number{};
+ std::uint32_t m_frequency_in_hz{};
+ std::uint64_t m_ticks{};
+ };
+
+} // namespace arch::devices
+
+#endif \ No newline at end of file
diff --git a/arch/x86_64/kapi/devices.cpp b/arch/x86_64/kapi/devices.cpp
new file mode 100644
index 0000000..b15503d
--- /dev/null
+++ b/arch/x86_64/kapi/devices.cpp
@@ -0,0 +1,35 @@
+#include "kapi/devices.hpp"
+
+#include "arch/bus/isa.hpp"
+#include "arch/devices/legacy_pit.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <cstdint>
+#include <utility>
+
+namespace kapi::devices
+{
+
+ namespace
+ {
+ constexpr auto pit_frequency_in_hz = std::uint32_t{100u};
+ }
+
+ auto init_platform_devices() -> void
+ {
+ kstd::println("[x86_64:devices] Initializing ISA bus...");
+
+ auto isa_major_number = kapi::devices::allocate_major_number();
+ auto isa_bus = kstd::make_unique<arch::bus::isa>(isa_major_number);
+
+ auto pit_major_number = kapi::devices::allocate_major_number();
+ auto pit = kstd::make_unique<arch::devices::legacy_pit>(pit_major_number, pit_frequency_in_hz);
+ isa_bus->add_child(std::move(pit));
+
+ auto & root_bus = get_root_bus();
+ root_bus.add_child(std::move(isa_bus));
+ }
+
+} // namespace kapi::devices \ No newline at end of file
diff --git a/arch/x86_64/src/bus/isa.cpp b/arch/x86_64/src/bus/isa.cpp
new file mode 100644
index 0000000..ff4ad71
--- /dev/null
+++ b/arch/x86_64/src/bus/isa.cpp
@@ -0,0 +1,14 @@
+#include "arch/bus/isa.hpp"
+
+#include "kapi/devices.hpp"
+
+#include <cstddef>
+
+namespace arch::bus
+{
+
+ isa::isa(std::size_t major)
+ : kapi::devices::bus{major, 0, "isa"}
+ {}
+
+} // namespace arch::bus \ No newline at end of file
diff --git a/arch/x86_64/src/cpu/initialization.cpp b/arch/x86_64/src/cpu/initialization.cpp
index 878fa07..b808c76 100644
--- a/arch/x86_64/src/cpu/initialization.cpp
+++ b/arch/x86_64/src/cpu/initialization.cpp
@@ -139,7 +139,7 @@ namespace arch::cpu
constexpr auto pic_cascade_address = std::uint8_t{0x04};
constexpr auto pic_cascade_slave_identity = std::uint8_t{0x02};
constexpr auto pic_use_8086_mode = std::uint8_t{0x01};
- constexpr auto pic_master_mask = std::uint8_t{0x01};
+ constexpr auto pic_master_mask = std::uint8_t{0x00};
constexpr auto pic_slave_mask = std::uint8_t{0x00};
pic_master_control_port::write(pic_init_command);
diff --git a/arch/x86_64/src/devices/legacy_pit.cpp b/arch/x86_64/src/devices/legacy_pit.cpp
new file mode 100644
index 0000000..a8df3c3
--- /dev/null
+++ b/arch/x86_64/src/devices/legacy_pit.cpp
@@ -0,0 +1,60 @@
+#include "arch/devices/legacy_pit.hpp"
+
+#include "kapi/devices.hpp"
+#include "kapi/devices/device.hpp"
+#include "kapi/interrupts.hpp"
+
+#include "arch/device_io/port_io.hpp"
+
+#include <cstddef>
+#include <cstdint>
+
+namespace arch::devices
+{
+
+ namespace
+ {
+ using command_port = io::port<0x43, std::uint8_t, io::port_write>;
+ using channel_0_port = io::port<0x40, std::uint8_t, io::port_write>;
+ using channel_1_port = io::port<0x41, std::uint8_t, io::port_write>;
+ using channel_2_port = io::port<0x42, std::uint8_t, io::port_write>;
+
+ constexpr auto base_frequency = 1'193'182u;
+ constexpr auto square_wave_mode = 0x36;
+ } // namespace
+
+ legacy_pit::legacy_pit(std::size_t major, std::uint32_t frequency_in_hz)
+ : kapi::devices::device{major, 0, "legacy_pit"}
+ , m_irq_number{0}
+ , m_frequency_in_hz{frequency_in_hz}
+ {}
+
+ auto legacy_pit::init() -> bool
+ {
+ auto divisor = static_cast<std::uint16_t>(base_frequency / m_frequency_in_hz);
+
+ kapi::interrupts::register_handler(m_irq_number, *this);
+
+ command_port::write<std::uint8_t>(square_wave_mode);
+ io::wait();
+ channel_0_port::write<std::uint8_t>(divisor & 0xff);
+ io::wait();
+ channel_0_port::write<std::uint8_t>(divisor >> 8 & 0xff);
+ io::wait();
+
+ return true;
+ }
+
+ auto legacy_pit::handle_interrupt(std::uint32_t irq_number) -> kapi::interrupts::status
+ {
+ if (irq_number != m_irq_number)
+ {
+ return kapi::interrupts::status::unhandled;
+ }
+
+ ++m_ticks;
+
+ return kapi::interrupts::status::handled;
+ }
+
+} // namespace arch::devices \ No newline at end of file
diff --git a/kapi/include/kapi/devices.hpp b/kapi/include/kapi/devices.hpp
new file mode 100644
index 0000000..5c01b2f
--- /dev/null
+++ b/kapi/include/kapi/devices.hpp
@@ -0,0 +1,36 @@
+#ifndef TEACHOS_KAPI_DEVICES_HPP
+#define TEACHOS_KAPI_DEVICES_HPP
+
+#include "kapi/devices/bus.hpp" // IWYU pragma: export
+#include "kapi/devices/device.hpp" // IWYU pragma: export
+#include "kapi/devices/manager.hpp" // IWYU pragma: export
+
+namespace kapi::devices
+{
+
+ //! @addtogroup kernel-defined
+ //! @{
+
+ //! Initialize the kernel's device management subsystem.
+ auto init() -> void;
+
+ //! Get the virtual system root bus.
+ //!
+ //! @warning This function will panic if the root bus has not been initialized.
+ //!
+ //! @return a reference to the root bus.
+ auto get_root_bus() -> bus &;
+
+ //! @}
+
+ //! @addtogroup platform-defined
+ //! @{
+
+ //! Initialize the platform's device tree.
+ auto init_platform_devices() -> void;
+
+ //! @}
+
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kapi/include/kapi/devices/bus.hpp b/kapi/include/kapi/devices/bus.hpp
new file mode 100644
index 0000000..ee774b7
--- /dev/null
+++ b/kapi/include/kapi/devices/bus.hpp
@@ -0,0 +1,95 @@
+#ifndef TEACHOS_KAPI_DEVICES_BUS_HPP
+#define TEACHOS_KAPI_DEVICES_BUS_HPP
+
+#include "kapi/devices/device.hpp"
+#include "kapi/devices/manager.hpp"
+#include "kapi/system.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <atomic>
+#include <cstddef>
+#include <utility>
+
+namespace kapi::devices
+{
+ //! A bus device that represents a logical/physical tree of devices and busses.
+ struct bus : device
+ {
+ //! Construct a bus with the given major number, minor number, and name.
+ //!
+ //! @param major The major number of the bus.
+ //! @param minor The minor number of the bus.
+ //! @param name The name of the bus.
+ bus(std::size_t major, std::size_t minor, kstd::string const & name)
+ : device(major, minor, name)
+ {}
+
+ //! Initialize the bus and all of its children.
+ //!
+ //! @return true iff. the bus and all of its children are healthy, false otherwise.
+ auto init() -> bool final
+ {
+ if (m_initialized.test_and_set())
+ {
+ return true;
+ }
+
+ if (!probe())
+ {
+ return false;
+ }
+
+ return std::ranges::fold_left(m_devices, true, [&](bool acc, auto & child) -> bool {
+ kstd::println("[kAPI:BUS] Initializing child device {}@{}", child->name(), name());
+ return child->init() && acc;
+ });
+ }
+
+ //! Attach a child device to this bus.
+ //!
+ //! Whenever a device is attached to a bus, the bus takes sole ownership of the device.
+ //!
+ //! @param child The child device to attach.
+ auto add_child(kstd::unique_ptr<device> child) -> void
+ {
+ auto observer = m_observers.emplace_back(child.get());
+ m_devices.push_back(std::move(child));
+ kapi::devices::register_device(*observer);
+
+ if (m_initialized.test())
+ {
+ kstd::println("[kAPI:BUS] Initializing child device {}@{}", observer->name(), name());
+ if (!observer->init())
+ {
+ kapi::system::panic("[kAPI:BUS] Failed to initialize child device");
+ }
+ }
+ }
+
+ [[nodiscard]] auto children() const -> kstd::vector<kstd::observer_ptr<device>> const &
+ {
+ return m_observers;
+ }
+
+ protected:
+ //! Probe the bus hardware state.
+ //!
+ //! @return true iff. the bus hardware is healthy, false otherwise.
+ auto virtual probe() -> bool
+ {
+ return true;
+ }
+
+ private:
+ kstd::vector<kstd::unique_ptr<device>> m_devices;
+ kstd::vector<kstd::observer_ptr<device>> m_observers;
+ std::atomic_flag m_initialized{};
+ };
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kapi/include/kapi/devices/manager.hpp b/kapi/include/kapi/devices/manager.hpp
new file mode 100644
index 0000000..7817fbc
--- /dev/null
+++ b/kapi/include/kapi/devices/manager.hpp
@@ -0,0 +1,53 @@
+#ifndef TEACHOS_KAPI_DEVICES_MANAGER_HPP
+#define TEACHOS_KAPI_DEVICES_MANAGER_HPP
+
+// IWYU pragma: private, include "kapi/devices.hpp"
+
+#include "kapi/devices/device.hpp"
+
+#include <kstd/memory>
+
+#include <cstddef>
+#include <string_view>
+
+namespace kapi::devices
+{
+
+ //! @addtogroup kernel-defined
+ //! @{
+
+ //! Ask the kernel to allocate a new major number.
+ //!
+ //! @return a new, unused major number.
+ auto allocate_major_number() -> std::size_t;
+
+ //! Register a new device with the kernel's device manager.
+ //!
+ //! @param device The device to register.
+ //! @return true if the device was registered successfully, false otherwise.
+ auto register_device(device & device) -> bool;
+
+ //! Unregister a device from the kernel's device manager.
+ //!
+ //! @param device The device to unregister.
+ //! @return true if the device was unregistered successfully, false otherwise.
+ auto unregister_device(device & device) -> bool;
+
+ //! Find a device by its major and minor numbers.
+ //!
+ //! @param major the major number of the device.
+ //! @param minor the minor number of the device.
+ //! @return a pointer to the device iff. the device was found, nullptr otherwise.
+ auto find_device(std::size_t major, std::size_t minor) -> kstd::observer_ptr<device>;
+
+ //! Find a device by its name.
+ //!
+ //! @param name the name of the device.
+ //! @return a pointer to the device iff. the device was found, nullptr otherwise.
+ auto find_device(std::string_view name) -> kstd::observer_ptr<device>;
+
+ //! @}
+
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt
index f283588..9868eb9 100644
--- a/kernel/CMakeLists.txt
+++ b/kernel/CMakeLists.txt
@@ -3,6 +3,7 @@ add_library("kernel_objs" OBJECT
"kapi/boot_modules.cpp"
"kapi/cio.cpp"
"kapi/cpu.cpp"
+ "kapi/devices.cpp"
"kapi/interrupts.cpp"
"kapi/memory.cpp"
"kapi/system.cpp"
@@ -17,6 +18,7 @@ add_library("kernel_objs" OBJECT
"src/memory.cpp"
"src/devices/block_device.cpp"
"src/devices/block_device_utils.cpp"
+ "src/devices/root_bus.cpp"
"src/devices/storage/controller.cpp"
"src/devices/storage/management.cpp"
"src/devices/storage/ram_disk/controller.cpp"
diff --git a/kernel/include/kernel/devices/root_bus.hpp b/kernel/include/kernel/devices/root_bus.hpp
new file mode 100644
index 0000000..660b715
--- /dev/null
+++ b/kernel/include/kernel/devices/root_bus.hpp
@@ -0,0 +1,16 @@
+#ifndef TEACHOS_KERNEL_DEVICES_ROOT_BUS_HPP
+#define TEACHOS_KERNEL_DEVICES_ROOT_BUS_HPP
+
+#include "kapi/devices/bus.hpp"
+
+namespace kernel::devices
+{
+
+ struct root_bus final : kapi::devices::bus
+ {
+ root_bus();
+ };
+
+} // namespace kernel::devices
+
+#endif \ No newline at end of file
diff --git a/kernel/kapi/devices.cpp b/kernel/kapi/devices.cpp
new file mode 100644
index 0000000..031f2c9
--- /dev/null
+++ b/kernel/kapi/devices.cpp
@@ -0,0 +1,87 @@
+#include "kapi/devices.hpp"
+
+#include "kapi/system.hpp"
+
+#include "kernel/devices/root_bus.hpp"
+
+#include <kstd/flat_map>
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <atomic>
+#include <cstddef>
+#include <optional>
+#include <string_view>
+#include <utility>
+
+namespace kapi::devices
+{
+
+ namespace
+ {
+ auto constinit next_major_number = std::atomic_size_t{1};
+ auto constinit root_bus = std::optional<kernel::devices::root_bus>{};
+ auto constinit device_tree = kstd::flat_map<std::pair<std::size_t, std::size_t>, kstd::observer_ptr<device>>{};
+ } // namespace
+
+ auto init() -> void
+ {
+ auto static is_initialized = std::atomic_flag{};
+ if (is_initialized.test_and_set())
+ {
+ return;
+ }
+
+ auto & bus = root_bus.emplace();
+ register_device(bus);
+ bus.init();
+ }
+
+ auto get_root_bus() -> bus &
+ {
+ if (!root_bus.has_value())
+ {
+ kapi::system::panic("[OS:DEV] Root bus not initialized!");
+ }
+ return *root_bus;
+ }
+
+ auto allocate_major_number() -> std::size_t
+ {
+ return next_major_number++;
+ }
+
+ auto register_device(device & device) -> bool
+ {
+ kstd::println("[OS:DEV] Registering device {}@{}:{}", device.name(), device.major(), device.minor());
+ return device_tree.emplace(std::pair{device.major(), device.minor()}, &device).second;
+ }
+
+ auto unregister_device(device &) -> bool
+ {
+ kstd::println("[OS:DEV] TODO: implement device deregistration");
+ return false;
+ }
+
+ auto find_device(std::size_t major, std::size_t minor) -> kstd::observer_ptr<device>
+ {
+ if (device_tree.contains(std::pair{major, minor}))
+ {
+ return device_tree.at(std::pair{major, minor});
+ }
+ return nullptr;
+ }
+
+ auto find_device(std::string_view name) -> kstd::observer_ptr<device>
+ {
+ for (auto const & [key, value] : device_tree)
+ {
+ if (value->name() == name)
+ {
+ return value;
+ }
+ }
+ return nullptr;
+ }
+
+} // namespace kapi::devices \ No newline at end of file
diff --git a/kernel/src/devices/root_bus.cpp b/kernel/src/devices/root_bus.cpp
new file mode 100644
index 0000000..43a35bf
--- /dev/null
+++ b/kernel/src/devices/root_bus.cpp
@@ -0,0 +1,12 @@
+#include "kernel/devices/root_bus.hpp"
+
+#include "kapi/devices.hpp"
+
+namespace kernel::devices
+{
+
+ root_bus::root_bus()
+ : kapi::devices::bus{0, 0, "system"}
+ {}
+
+} // namespace kernel::devices \ No newline at end of file
diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp
index 37b4c5b..2eaa2d8 100644
--- a/kernel/src/main.cpp
+++ b/kernel/src/main.cpp
@@ -1,6 +1,7 @@
#include "kapi/boot_modules.hpp"
#include "kapi/cio.hpp"
#include "kapi/cpu.hpp"
+#include "kapi/devices.hpp"
#include "kapi/interrupts.hpp"
#include "kapi/memory.hpp"
#include "kapi/system.hpp"
@@ -172,13 +173,21 @@ auto main() -> int
kstd::println("[OS] IO subsystem initialized.");
kapi::cpu::init();
- kapi::interrupts::enable();
kapi::memory::init();
kernel::memory::init_heap(kapi::memory::heap_base);
kstd::println("[OS] Memory subsystem initialized.");
kapi::system::memory_initialized();
+ kapi::devices::init();
+ kstd::println("[OS] System root bus initialized.");
+
+ kapi::devices::init_platform_devices();
+ kstd::println("[OS] Platform devices initialized.");
+
+ kapi::interrupts::enable();
+ kstd::println("[OS] Interrupts enabled.");
+
kapi::boot_modules::init();
kstd::println("[OS] Boot module registry initialized.");
diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt
index ec0f441..240118e 100644
--- a/libs/kstd/CMakeLists.txt
+++ b/libs/kstd/CMakeLists.txt
@@ -44,6 +44,7 @@ else()
add_executable("kstd_tests"
"tests/src/flat_map.cpp"
"tests/src/vector.cpp"
+ "tests/src/observer_ptr.cpp"
"tests/src/os_panic.cpp"
"tests/src/string.cpp"
)
diff --git a/libs/kstd/include/kstd/bits/flat_map.hpp b/libs/kstd/include/kstd/bits/flat_map.hpp
index 9455549..fe46203 100644
--- a/libs/kstd/include/kstd/bits/flat_map.hpp
+++ b/libs/kstd/include/kstd/bits/flat_map.hpp
@@ -45,7 +45,7 @@ namespace kstd::bits
template<std::size_t Index>
requires(Index >= 0 && Index <= 1)
- constexpr auto get() const noexcept -> decltype(auto)
+ [[nodiscard]] constexpr auto get() const noexcept -> decltype(auto)
{
if constexpr (Index == 0)
{
diff --git a/libs/kstd/include/kstd/bits/observer_ptr.hpp b/libs/kstd/include/kstd/bits/observer_ptr.hpp
new file mode 100644
index 0000000..1c5da15
--- /dev/null
+++ b/libs/kstd/include/kstd/bits/observer_ptr.hpp
@@ -0,0 +1,163 @@
+#ifndef KSTD_OBSERVER_PTR_HPP
+#define KSTD_OBSERVER_PTR_HPP
+
+// IWYU pragma: private, include <kstd/memory>
+
+#include "kstd/os/error.hpp"
+
+#include <compare>
+#include <concepts>
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+
+namespace kstd
+{
+
+ template<typename ElementType>
+ struct observer_ptr
+ {
+ //! The type of the element being pointed to.
+ using element_type = ElementType;
+
+ //! Construct an empty observer pointer.
+ constexpr observer_ptr() noexcept = default;
+
+ //! Construct an empty observer pointer from a null pointer.
+ constexpr observer_ptr(std::nullptr_t) noexcept {}
+
+ //! Construct an observer pointer from a raw pointer.
+ constexpr explicit observer_ptr(element_type * pointer)
+ : m_ptr{pointer}
+ {}
+
+ //! Construct an observer pointer from another observer pointer.
+ template<typename OtherElementType>
+ requires std::convertible_to<OtherElementType *, ElementType *>
+ constexpr observer_ptr(observer_ptr<OtherElementType> other) noexcept
+ : m_ptr{other.get()}
+ {}
+
+ //! Copy construct an observer pointer.
+ constexpr observer_ptr(observer_ptr const & other) noexcept = default;
+
+ //! Move construct an observer pointer.
+ constexpr observer_ptr(observer_ptr && other) noexcept = default;
+
+ //! Copy assign an observer pointer.
+ constexpr auto operator=(observer_ptr const & other) noexcept -> observer_ptr & = default;
+
+ //! Move assign an observer pointer.
+ constexpr auto operator=(observer_ptr && other) noexcept -> observer_ptr & = default;
+
+ //! Stop watching the the watched object.
+ //!
+ //! @return The currently watched object, or nullptr if no object is being watched.
+ [[nodiscard]] constexpr auto release() noexcept -> element_type *
+ {
+ return std::exchange(m_ptr, nullptr);
+ }
+
+ //! Reset the observer pointer.
+ //!
+ //! @param pointer The new object to watch.
+ constexpr auto reset(element_type * pointer = nullptr) noexcept -> void
+ {
+ m_ptr = pointer;
+ }
+
+ //! Swap the observer pointer with another observer pointer.
+ //!
+ //! @param other The other observer pointer to swap with.
+ constexpr auto swap(observer_ptr & other) noexcept -> void
+ {
+ std::swap(m_ptr, other.m_ptr);
+ }
+
+ //! Get the currently watched object.
+ //!
+ //! @return The currently watched object, or nullptr if no object is being watched.
+ [[nodiscard]] constexpr auto get() const noexcept -> element_type *
+ {
+ return m_ptr;
+ }
+
+ //! Check if the observer pointer is watching an object.
+ //!
+ //! @return True if the observer pointer is watching an object, false otherwise.
+ [[nodiscard]] constexpr explicit operator bool() const noexcept
+ {
+ return m_ptr != nullptr;
+ }
+
+ //! Get the currently watched object.
+ //!
+ //! @return A reference to the currently watched object.
+ [[nodiscard]] constexpr auto operator*() const -> std::add_lvalue_reference_t<element_type>
+ {
+ throw_on_null();
+ return *m_ptr;
+ }
+
+ //! Get the currently watched object.
+ //!
+ //! @return A pointer to the currently watched object.
+ [[nodiscard]] constexpr auto operator->() const -> element_type *
+ {
+ throw_on_null();
+ return m_ptr;
+ }
+
+ //! Convert the observer pointer to a raw pointer.
+ //!
+ //! @return A pointer to the currently watched object.
+ constexpr explicit operator element_type *() const noexcept
+ {
+ return m_ptr;
+ }
+
+ //! Compare the observer pointer with another observer pointer.
+ //!>
+ //! @param other The other observer pointer to compare with.
+ //! @return The result of the comparison.
+ constexpr auto operator<=>(observer_ptr const & other) const noexcept -> std::strong_ordering = default;
+
+ private:
+ //! Throw an exception if the observer pointer is null.
+ //!
+ //! @throws std::runtime_error if the observer pointer is null.
+ constexpr auto throw_on_null() const -> void
+ {
+ if (m_ptr == nullptr)
+ {
+ os::panic("[kstd:observer_ptr] Dereferencing a null observer pointer");
+ }
+ }
+
+ //! The raw pointer to the watched object.
+ ElementType * m_ptr{};
+ };
+
+ //! Swap two observer pointers.
+ //!
+ //! @param lhs The first observer pointer to swap.
+ //! @param rhs The second observer pointer to swap.
+ template<typename ElementType>
+ constexpr auto swap(observer_ptr<ElementType> & lhs, observer_ptr<ElementType> & rhs) noexcept -> void
+ {
+ lhs.swap(rhs);
+ }
+
+ //! Create an observer pointer from a raw pointer.
+ //!
+ //! @param pointer The raw pointer to create an observer pointer from.
+ //! @return An observer pointer to the given raw pointer.
+ template<typename ElementType>
+ constexpr auto make_observer(ElementType * pointer) noexcept -> observer_ptr<ElementType>
+ {
+ return observer_ptr<ElementType>{pointer};
+ }
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/unique_ptr.hpp b/libs/kstd/include/kstd/bits/unique_ptr.hpp
index e0870b1..3d803b4 100644
--- a/libs/kstd/include/kstd/bits/unique_ptr.hpp
+++ b/libs/kstd/include/kstd/bits/unique_ptr.hpp
@@ -16,6 +16,9 @@ namespace kstd
template<typename T>
struct unique_ptr
{
+ template<typename U>
+ friend struct unique_ptr;
+
/**
* @brief Constructor.
*
@@ -40,6 +43,12 @@ namespace kstd
*/
unique_ptr(unique_ptr const &) = delete;
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ unique_ptr(unique_ptr<U> && other) noexcept
+ : pointer{std::exchange(other.pointer, nullptr)}
+ {}
+
/**
* @brief Deleted copy assignment operator to enforce unique ownership.
*/
@@ -51,10 +60,8 @@ namespace kstd
* @param other Unique pointer to move from.
*/
unique_ptr(unique_ptr && other) noexcept
- : pointer(other.pointer)
- {
- other.pointer = nullptr;
- }
+ : pointer{std::exchange(other.pointer, nullptr)}
+ {}
/**
* @brief Move assignment operator. Transfers ownership from other to *this as if by calling reset(r.release()).
@@ -67,8 +74,7 @@ namespace kstd
if (this != &other)
{
delete pointer;
- pointer = other.pointer;
- other.pointer = nullptr;
+ pointer = std::exchange(other.pointer, nullptr);
}
return *this;
}
@@ -123,9 +129,7 @@ namespace kstd
*/
auto release() -> T *
{
- T * temp = pointer;
- pointer = nullptr;
- return temp;
+ return std::exchange(pointer, nullptr);
}
/**
@@ -139,8 +143,7 @@ namespace kstd
*/
auto reset(T * ptr = nullptr) -> void