// // Created by erki on 12/09/23. // #pragma once #include #include #include #include #include #include namespace cpptick { template concept TrivialClass = std::is_trivial_v && std::is_trivially_destructible_v; template concept CopyableClass = !TrivialClass && std::copy_constructible; template concept SlotArgument = TrivialClass || CopyableClass; struct ArgStorage { std::array buffer; std::array pointer_buffer; std::array::iterator pointer_buffer_head; std::size_t space_remaining; ArgStorage() = default; ArgStorage(const ArgStorage&) = default; ArgStorage(ArgStorage&&) = default; void reset() { space_remaining = buffer.size(); pointer_buffer_head = pointer_buffer.begin(); *pointer_buffer_head = buffer.data(); } template void pushArg(T&& arg) { if (pointer_buffer_head == pointer_buffer.end() || space_remaining == 0) std::exit(1); void* memory_location = *pointer_buffer_head; if (std::align(alignof(T), sizeof(T), memory_location, space_remaining) == nullptr) std::exit(2); std::memcpy(memory_location, &arg, sizeof(T)); pointer_buffer_head++; if (pointer_buffer_head != pointer_buffer.end()) { *pointer_buffer_head = (char*)memory_location + sizeof(T); space_remaining -= sizeof(T); } } template void pushArg(T&& arg) { if (pointer_buffer_head == pointer_buffer.end() || space_remaining == 0) std::exit(1); void* memory_location = *pointer_buffer_head; if (std::align(alignof(T), sizeof(T), memory_location, space_remaining) == nullptr) std::exit(2); new (memory_location) T{arg}; pointer_buffer_head++; if (pointer_buffer_head != pointer_buffer.end()) { *pointer_buffer_head = (char*)memory_location + sizeof(T); space_remaining -= sizeof(T); } } template void cleanUp(const std::size_t) { } template void cleanUp(const std::size_t index) { T* item = reinterpret_cast(pointer_buffer[index]); item->~T(); } template void cleanUp(std::index_sequence) { (cleanUp(Is), ...); } template void cleanUp() { cleanUp(std::make_index_sequence{}); } template T& at(const std::size_t idx) { return *((T*)pointer_buffer[idx]); } template const T& at(const std::size_t idx) const { return *((T*)pointer_buffer[idx]); } }; struct Scheduler { using StoredCall = std::pair*, ArgStorage>; Utility::Ringbuffer stored_calls; void tick() { if (!stored_calls.empty()) { auto* f = stored_calls.begin()->first; auto& args = stored_calls.begin()->second; (*f)(args); stored_calls.pop_front(); } } ArgStorage& storeCall(Utility::IFunction* call) { // @todo: handle overflow... // still constructs double. stored_calls.emplace_back(call, ArgStorage{}); auto& storage = stored_calls.back().second; storage.reset(); return storage; } }; template struct Slot; template struct Slot { std::array*, 12> signals; Scheduler* scheduler; Utility::FunctionOwned, void (ArgStorage&)> invoke_ptr; Slot() = delete; explicit Slot(Scheduler* sched) : scheduler(sched) , invoke_ptr(*this, &Slot::callUp) { signals.fill(nullptr); } Slot(const Slot&) = delete; Slot(Slot&&) = delete; Slot& operator=(const Slot&) = delete; Slot& operator=(Slot&&) = delete; void connect(Utility::IFunction* signal) { for (auto*& callable : signals) { if (callable == nullptr) { callable = signal; break; } } } Utility::IFunction* connect(R (*func)(Args...)) { auto* f = new Utility::Function(func); connect(f); return f; } template Utility::IFunction* connect(Source& src, R (Source::* func)(Args...)) { auto* f = new Utility::FunctionOwned(src, func); connect(f); return f; } template void callUpFunction(Utility::IFunction* func, ArgStorage& args, std::index_sequence) { (*func)(std::forward(args.at(Is))...); } void callUpFunction(Utility::IFunction* func, ArgStorage& args) { callUpFunction(func, args, std::make_index_sequence{}); } void callUp(ArgStorage& args) { for (auto* f : signals) { if (f) callUpFunction(f, args); else break; } args.cleanUp(); } void invoke(Args... args) { ArgStorage& storage = scheduler->storeCall(&invoke_ptr); (storage.pushArg(std::forward(args)), ...); } }; }