Explorar el Código

Add: EventHubクラスを新設

Satoshi Yoneda hace 1 mes
padre
commit
e10e85834a
Se han modificado 3 ficheros con 129 adiciones y 16 borrados
  1. 23 12
      main.cpp
  2. 97 2
      tinykernel/include/tinykernel/actor.hpp
  3. 9 2
      tinykernel/src/scheduler.cpp

+ 23 - 12
main.cpp

@@ -19,10 +19,9 @@
     } while (0)
 
 // ---------------------------------------------------------------------------
-// Actor demonstration: Receiver Actor waiting for typed messages by-value.
-// The message (including the embedded string payload) is copied directly into the
-// actor's queue, so sender tasks can pass local/stack variables safely without
-// lifetime or dangling pointer issues.
+// Actor demonstration: 1-to-N Pub/Sub via EventHub.
+// Senders call tk::publish(msg) without needing to know any Actor instance!
+// All actors subscribed to AppMessage receive their own copy in their queues.
 struct AppMessage {
     uint32_t seq;
     char text[32];  // Embedded payload held safely by the queue!
@@ -31,7 +30,6 @@ struct AppMessage {
 class LedActor : public tk::Actor<AppMessage, 8> {
 protected:
     void on_start() override {
-        // Hardware initialization within task context
         gpio_init(25);
         gpio_set_dir(25, GPIO_OUT);
         gpio_put(25, 0);
@@ -41,27 +39,38 @@ protected:
     void on_message(const AppMessage& msg) override {
         bool state = !gpio_get(25);
         gpio_put(25, state);
-        P("LedActor: received seq=%lu text=\"%s\" -> LED %s, now=%llu ms\n",
+        P("LedActor: seq=%lu text=\"%s\" -> LED %s, now=%llu ms\n",
           (unsigned long)msg.seq, msg.text, state ? "ON" : "OFF", (unsigned long long)tk::now_ms());
     }
 };
 
+// Second actor subscribed to the exact same AppMessage type!
+class LogActor : public tk::Actor<AppMessage, 8> {
+protected:
+    void on_message(const AppMessage& msg) override {
+        P("LogActor:  [AUDIT] seq=%lu text=\"%s\", now=%llu ms\n",
+          (unsigned long)msg.seq, msg.text, (unsigned long long)tk::now_ms());
+    }
+};
+
 static LedActor g_led_actor;
+static LogActor g_log_actor;
 
-// Sender task: periodically posts messages to LedActor
+// Sender task: publishes messages anonymously (without knowing who the actors are!)
 static void task_sender(void* arg) {
     (void)arg;
     uint32_t seq = 0;
     for (;;) {
-        tk::sleep_ms(600);
+        tk::sleep_ms(800);
         // Local variable allocated on task_sender's stack
         AppMessage msg{};
         msg.seq = seq++;
         snprintf(msg.text, sizeof(msg.text), "Ping #%lu", (unsigned long)msg.seq);
 
-        P("Sender: posting text=\"%s\"...\n", msg.text);
-        g_led_actor.post(msg);  // Copied by-value into g_led_actor's queue!
-    }  // msg on stack is destroyed here, but g_led_actor safely holds the copy!
+        // Anonymous 1-to-N publish!
+        size_t n = tk::publish(msg);
+        P("Sender: published text=\"%s\" to %lu subscriber(s)!\n", msg.text, (unsigned long)n);
+    }
 }
 
 // ---------------------------------------------------------------------------
@@ -158,6 +167,7 @@ int main() {
 
     printf("tinykernel: registering dynamic tasks...\n");
     g_led_actor.start(tk::PRIO_NORMAL);
+    g_log_actor.start(tk::PRIO_NORMAL);
     auto h_sender  = tk::create(task_sender, nullptr, tk::PRIO_NORMAL);
     auto h_mtx_a   = tk::create(task_mutex_worker, const_cast<char*>("Mtx-A"), tk::PRIO_NORMAL);
     auto h_mtx_b   = tk::create(task_mutex_worker, const_cast<char*>("Mtx-B"), tk::PRIO_NORMAL);
@@ -165,7 +175,8 @@ int main() {
     auto h_sig     = tk::create(task_sem_signaler, nullptr, tk::PRIO_NORMAL);
     auto h_preempt = tk::create(task_preempt_demo, nullptr, tk::PRIO_NORMAL);
 
-    if (!g_led_actor.handle().is_valid() || !h_sender.is_valid() ||
+    if (!g_led_actor.handle().is_valid() || !g_log_actor.handle().is_valid() ||
+        !h_sender.is_valid() ||
         !h_mtx_a.is_valid() || !h_mtx_b.is_valid() ||
         !h_waiter.is_valid() || !h_sig.is_valid() || !h_preempt.is_valid()) {
         printf("tinykernel: task creation FAILED\n");

+ 97 - 2
tinykernel/include/tinykernel/actor.hpp

@@ -13,16 +13,111 @@ struct Message {
     uintptr_t arg;
 };
 
+// ===========================================================================
+// EventHub: Ultra-lightweight type-based Pub/Sub dispatcher
+// ===========================================================================
+template <typename MsgType, size_t MaxSubscribers = 4>
+class EventHub {
+    struct Subscriber {
+        void* actor{nullptr};
+        bool (*post_fn)(void*, const MsgType&){nullptr};
+    };
+
+    static inline Subscriber subs_[MaxSubscribers]{};
+    static inline size_t count_{0};
+
+public:
+    static bool subscribe(void* actor, bool (*post_fn)(void*, const MsgType&)) {
+        uint32_t save = detail::sched_lock();
+        if (count_ >= MaxSubscribers) {
+            detail::sched_unlock(save);
+            return false;
+        }
+        for (size_t i = 0; i < count_; ++i) {
+            if (subs_[i].actor == actor) {
+                detail::sched_unlock(save);
+                return true;  // Already registered
+            }
+        }
+        subs_[count_++] = {actor, post_fn};
+        detail::sched_unlock(save);
+        return true;
+    }
+
+    static bool unsubscribe(void* actor) {
+        uint32_t save = detail::sched_lock();
+        for (size_t i = 0; i < count_; ++i) {
+            if (subs_[i].actor == actor) {
+                for (size_t j = i; j + 1 < count_; ++j) {
+                    subs_[j] = subs_[j + 1];
+                }
+                count_--;
+                detail::sched_unlock(save);
+                return true;
+            }
+        }
+        detail::sched_unlock(save);
+        return false;
+    }
+
+    // Publish message to all registered actors
+    static size_t publish(const MsgType& msg) {
+        Subscriber local_subs[MaxSubscribers];
+        size_t n = 0;
+        {
+            uint32_t save = detail::sched_lock();
+            n = count_;
+            for (size_t i = 0; i < n; ++i) {
+                local_subs[i] = subs_[i];
+            }
+            detail::sched_unlock(save);
+        }
+
+        size_t delivered = 0;
+        for (size_t i = 0; i < n; ++i) {
+            if (local_subs[i].post_fn && local_subs[i].post_fn(local_subs[i].actor, msg)) {
+                delivered++;
+            }
+        }
+        return delivered;
+    }
+
+    static size_t subscriber_count() {
+        uint32_t save = detail::sched_lock();
+        size_t n = count_;
+        detail::sched_unlock(save);
+        return n;
+    }
+};
+
+// Global publish functions (No actor instance required)
+template <typename MsgType>
+inline size_t publish(const MsgType& msg) {
+    return EventHub<MsgType>::publish(msg);
+}
+
+inline size_t publish(uint32_t id, uintptr_t arg = 0) {
+    return publish(Message{id, arg});
+}
+
+// ===========================================================================
+// Actor: Message-driven task base class
+// ===========================================================================
 template <typename MsgType = Message, size_t QueueSize = 8>
 class Actor {
     static_assert(QueueSize > 0, "QueueSize must be greater than 0");
 
 public:
     Actor() = default;
-    virtual ~Actor() = default;
+    virtual ~Actor() {
+        EventHub<MsgType>::unsubscribe(this);
+    }
 
-    // タスクとして起動する。tk::start() より前に呼ぶ。
+    // タスクとして起動し、EventHub に自身を自動登録する
     bool start(uint32_t prio = PRIO_NORMAL) {
+        EventHub<MsgType>::subscribe(this, [](void* obj, const MsgType& m) {
+            return static_cast<Actor<MsgType, QueueSize>*>(obj)->post(m);
+        });
         handle_ = tk::create(task_entry, this, prio);
         return handle_.is_valid();
     }

+ 9 - 2
tinykernel/src/scheduler.cpp

@@ -45,11 +45,19 @@ static uint8_t g_rr_last[16] = {0};
 
 static spin_lock_t* g_sched_lock = nullptr;
 
+static inline void ensure_sched_lock(void) {
+    if (!g_sched_lock) {
+        int lock_num = spin_lock_claim_unused(true);
+        g_sched_lock = spin_lock_instance(lock_num);
+    }
+}
+
 constexpr uint32_t kIdlePrio = 15u;
 constexpr int32_t kSliceTicks = (int32_t)(((uint64_t)TK_TIME_SLICE_MS * TK_TICKS_PER_SEC + 999u) / 1000u);
 static_assert(kSliceTicks >= 1, "time slice must be at least one tick");
 
 static inline uint32_t sched_lock(void) {
+    ensure_sched_lock();
     return spin_lock_blocking(g_sched_lock);
 }
 
@@ -240,8 +248,7 @@ void tk::start() {
     if (g_started) for (;;) __asm__ volatile("wfi");
     g_started = true;
 
-    int lock_num = spin_lock_claim_unused(true);
-    g_sched_lock = spin_lock_instance(lock_num);
+    ensure_sched_lock();
 
     // Core 0 Idle Task
     TaskSlot& idle0 = g_slots[0];