A pretty large patch not worth breaking up into separate commits

[*] Split up Aurora Async
[*] Split Async app into seperate ThreadPool concept
[*] Fix various OSThread bugs and tls transfer issues
[*] Set default affinity to 0xFFFFFFFF
[*] Update Build script
[+] Add AuTuplePopFront
[+] New Network Interface (unimplemented)
[*] Stub out the interfaces required for a better logger
[*] Fix Win32 ShellExecute bug; windows 11 struggles without explicit com init per the docs - now deferring to thread pool
[*] Update gitignore
[*] Follow XDG home standard
[*] Refactor some namespaces to use the shorthand aliases
[*] Various stability fixes
master
Reece Wilson 2021-11-05 17:34:23 +00:00
parent 4109852d06
commit 99c5e1fa65
90 changed files with 4593 additions and 2258 deletions

192
.gitignore vendored
View File

@ -1,26 +1,208 @@
# Aurora's general purpose JS/TS/C/C++/Go vs/vscode/intellij/codelite gitignore reference
# Almost usable for Java and Qt
# Aurora build configuration
Build_CompilerWorkingDirectory/*
Build_Developers/*
Build_Ship/*
Build_Internal/*
Build_Develop/*
*.vcxproj
*.vcxproj.filters
*.vcxproj.user
Build_Stage/*
Build_Ship/*
Build_Workspace/*
# License Headers VS extension
*.licenseheader
# Binaries / object files
*.dll
*.exe
*.obj
*.so
*.so.*
*.la
*.lai
*.pdb
*.idb
*.exe~
*.obj
*.dynlib
*.dylib
*.lib
*.d
*.o
*.a
*.la
*.slo
*.lo
*.out
.vs
# go unit test
*.test
# Autogenerated project files
compile_flags.txt
*.mk
*.project
*cmake
Makefile
*.vcxproj
*.xcodeproj
# IDE trash
.vscode
.vs
/*.gcno
.intellij
.clion
Makefile
*.vcxproj.filters
*.vcxproj.user
*.tlog
# OSX
.DS_Store
.AppleDouble
.LSOverride
xcuserdata/
# Win32
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
*.lnk
# Linux is trash and cant hotswap like NT
.nfs*
.fuse_hidden*
# Ninja
.ninja_deps
.ninja_log
# PID locks
*.pid
*.pid.lock
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# JetBrains
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-*/
# Android Studio
.idea/caches/build_file_checksums.ser
# why would we ever ship this dir?
.idea/caches/*
# NodeJS
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# node-waf configuration
.lock-wscript
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# VS Code Extensions
.vscode-test
# Qt unit tests
target_wrapper.*
# QtCreator
*.autosave
# QtCreator Qml
*.qmlproject.user
*.qmlproject.user.*
# QtCreator CMake
CMakeLists.txt.user*
# QtCreator 4.8< compilation database
compile_commands.json
# QtCreator local machine specific files for imported projects
*creator.user*
*_qmlcache.qrc
# QT cache and user files
/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
# Java trash
hs_err_pid*
.gradle
gradle-app.setting
!gradle-wrapper.jar
.gradletasknamecache
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar

View File

@ -7,8 +7,8 @@
"staticImpDefines": "AURORA_ENGINE_KERNEL_STATIC",
"defines": [],
"soft-depends": ["wxwidgets", "glm"],
"depends": ["AuroraInterfaces", "mimalloc", "uuid", "fmt", "json", "bzip2", "ltc", "o1heap", "zstd", "zlib", "lz4", "mbedtls"],
"include-depends": ["fmt", "uuid", "AuroraInterfaces"],
"depends": ["AuroraInterfaces", "AuroraEnum", "mimalloc", "uuid", "fmt", "json", "bzip2", "ltc", "o1heap", "zstd", "zlib", "lz4", "mbedtls"],
"include-depends": ["fmt", "uuid", "AuroraInterfaces", "AuroraEnum"],
"features": ["guess-platform-code"],
"linkSources": "Source/Alloc.cpp",
"actions": [
@ -19,4 +19,4 @@
}
}
]
}
}

View File

@ -7,305 +7,52 @@
***/
#pragma once
namespace Aurora::Loop
{
class ILoopSource;
}
#include "AsyncTypes.hpp"
#include "IWorkItem.hpp"
#include "IWorkItemHandler.hpp"
#include "IThreadPool.hpp"
#include "IAsyncApp.hpp"
#include "Jobs.hpp"
#include "Tasks.hpp"
namespace Aurora::Async
{
class IWorkItem;
class IAsyncApp;
AUKN_SYM IAsyncApp *GetAsyncApp();
struct AVoid
{
AuUInt8 x[1];
};
AUKN_SYM IAsyncApp *GetAsyncApp();
/// ThreadGroup_t:
/// 0 = system main thread
/// 1+ = user defined
using ThreadGroup_t = AuUInt8;
/// ThreadId_t:
/// -1 = invalid
/// index = tid/runner id
using ThreadId_t = AuUInt16;
static const ThreadId_t kThreadIdAny = -1;
struct WorkerId_t : AuPair<ThreadGroup_t, ThreadId_t>
{
WorkerId_t() : AuPair<ThreadGroup_t, ThreadId_t>(0, 0)
{}
WorkerId_t(ThreadGroup_t group) : AuPair<ThreadGroup_t, ThreadId_t>(group, kThreadIdAny)
{}
WorkerId_t(ThreadGroup_t group, ThreadId_t id) : AuPair<ThreadGroup_t, ThreadId_t>(group, id)
{}
WorkerId_t(const WorkerId_t &cpy) : AuPair<ThreadGroup_t, ThreadId_t>(cpy.first, cpy.second)
{}
};
struct WorkPriv
{
AuUInt32 magic;
};
struct IWorkItemHandler
{
enum class EProcessNext
{
eInvalid = -1,
eFinished = 0,
eRerun,
eSchedule,
eFailed
};
struct ProcessInfo
{
ProcessInfo(bool finished) : type(finished ? EProcessNext::eFinished : EProcessNext::eFailed) {}
ProcessInfo(EProcessNext type) : type(type) {}
ProcessInfo(const AuList<AuSPtr<IWorkItem>> &blockedBy) : type(EProcessNext::eSchedule), waitFor(blockedBy) {}
// ...
EProcessNext type;
AuList<AuSPtr<IWorkItem>> waitFor;
AuUInt32 reschedMs;
AuUInt64 reschedNs;
};
virtual void DispatchFrame(ProcessInfo &info) = 0;
/// A really terrible name for the overloadable method that serves as the critical failure callback
/// You have a 'shutdown'/free function you can overload, it's called the dtor
/// Don't moan about the shitty naming of this, im not refactoring it
virtual void Shutdown() = 0;
virtual void *GetPrivateData() { return nullptr; }
};
template<class Info_t = AVoid, class Result_t = AVoid>
struct FJob
{
std::function<void(const Info_t &, const Result_t &)> onSuccess = 0;
std::function<void(const Info_t &)> onFailure = 0;
};
template<class Info_t = AVoid, class Result_t = AVoid>
static inline FJob<Info_t, Result_t> JobFromConsumer(const AuConsumer<const Info_t &, const Result_t &> &onSuccess)
{
FJob<Info_t, Result_t> ret;
ret.onSuccess = [=](const Info_t &in, const Result_t &a)
{
onSuccess(in, a);
};
return ret;
}
template<class Info_t = AVoid, class Result_t = AVoid>
static inline FJob<Info_t, Result_t> JobFromConsumer(const AuConsumer<const Info_t &, const Result_t &> &onSuccess, const AuConsumer<const Info_t &, bool/*neverDispatched*/> &onFailure)
{
FJob<Info_t, Result_t> ret;
ret.onSuccess = [=](const Info_t &in, const Result_t &a)
{
onSuccess(in, a);
};
ret.onFailure = [=](const Info_t &a)
{
onFailure(a, true);
};
return ret;
}
template<class Info_t = AVoid, class Result_t = AVoid>
static inline FJob<Info_t, Result_t> JobFromConsumer(const AuConsumer<const Info_t &, const Result_t &> &onSuccess, const AuConsumer<const Info_t &> &onFailure)
{
FJob<Info_t, Result_t> ret;
ret.onSuccess = [=](const Info_t &in, const Result_t &a)
{
onSuccess(in, a);
};
ret.onFailure = [=](const Info_t &a)
{
onFailure(a);
};
return ret;
}
template<class Info_t = AVoid, class Result_t = AVoid, class BaseInfo_t = AVoid, class BaseResult_t = AVoid>
static inline FJob<Info_t, Result_t> JobFromDerivedJob(const FJob<BaseInfo_t, BaseResult_t> &reference)
{
FJob<Info_t, Result_t> ret;
ret.onSuccess = [=](const Info_t &in, const Result_t &a)
{
if (reference.onSuccess)
{
reference.onSuccess(in, a);
}
};
ret.onFailure = [=](const Info_t &a)
{
if (reference.onFailure)
{
reference.onSuccess(a);
}
};
return ret;
}
using FVoidJob = FJob<AVoid, AVoid>;
template<class Info_t = AVoid, class Result_t = AVoid>
struct CJob
{
void(* onSuccess)(const Info_t &, const Result_t &);
void(* onFailure)(const Info_t &);
};
template<class Info_t = AVoid, class Result_t = AVoid>
struct FTask
{
std::function<Result_t(const Info_t &)> onFrame = 0;
};
using FVoidTask = FTask<AVoid, AVoid>;
template<typename Info_t = AVoid, typename Out_t = AVoid, class ClazzImpl>
FTask<Info_t, Out_t> TaskFromConsumerRefT(ClazzImpl &&func)
{
FTask<Info_t, Out_t> ret;
ret.onFrame = [callable = func](const Info_t &in) -> Out_t
{
if constexpr (std::is_same_v<Out_t, AVoid>)
{
callable(in);
return {};
}
else
{
return callable(in);
}
};
return ret;
}
template<typename Info_t = AVoid, typename Out_t = AVoid>
FTask<Info_t, Out_t> TaskFromVoidVoid(const AuVoidFunc &func)
{
FTask<Info_t, Out_t> ret;
ret.onFrame = [callable = func](const Info_t &in) -> Out_t
{
callable();
return {};
};
return ret;
}
template<class Info_t = AVoid, class Result_t = AVoid>
struct CTask
{
Result_t(* onFrame)(const Info_t &);
};
class IWorkItem
{
public:
virtual AuSPtr<IWorkItem> WaitFor(const AuSPtr<IWorkItem> &workItem) = 0;
virtual AuSPtr<IWorkItem> WaitFor(const AuList<AuSPtr<IWorkItem>> &workItem) = 0;
// ms = time relative to the current time
virtual AuSPtr<IWorkItem> SetSchedTime(AuUInt32 ms) = 0;
// ns = time relative to the current time
virtual AuSPtr<IWorkItem> SetSchedTimeNs(AuUInt64 ns) = 0;
// ms = time relative to the time at which the work item would otherwise dispatch
virtual AuSPtr<IWorkItem> AddDelayTime(AuUInt32 ms) = 0;
// ns = time relative to the time at which the work item would otherwise dispatch
virtual AuSPtr<IWorkItem> AddDelayTimeNs(AuUInt64 ns) = 0;
virtual AuSPtr<IWorkItem> Then(const AuSPtr<IWorkItem> &next) = 0;
virtual AuSPtr<IWorkItem> Dispatch() = 0;
virtual bool BlockUntilComplete() = 0;
virtual bool HasFinished() = 0;
virtual bool HasFailed() = 0;
virtual void Cancel() = 0;
virtual void *GetPrivateData() = 0;
virtual AuOptional<void *> ToWorkResultT() = 0;
};
///
AUKN_SYM WorkerPId_t GetCurrentWorkerPId();
/// Async app only | Thread pools must use the IThreadPool::NewFence function
AUKN_SYM AuSPtr<IWorkItem> NewWorkItem(const WorkerId_t &worker, const AuSPtr<IWorkItemHandler> &task, bool supportsBlocking = false);
AUKN_SYM AuSPtr<IWorkItem> NewWorkItem(const WorkerPId_t &worker, const AuSPtr<IWorkItemHandler> &task, bool supportsBlocking = false);
/// Async app only | Thread pools must use the IThreadPool::NewFence function
AUKN_SYM AuSPtr<IWorkItem> NewFence();
class IAsyncApp
{
public:
// Main thread logic
virtual void Start() = 0;
virtual void Main() = 0;
virtual void Shutdown() = 0;
virtual bool Exiting() = 0;
virtual void SetConsoleCommandDispatcher(WorkerId_t id) = 0;
/// Allocates a new thread pool for usage
AUKN_SYM AuSPtr<IThreadPool> NewThreadPool();
// Spawning
virtual bool Spawn(WorkerId_t workerId) = 0;
// Event runner threads release upon encountering a zero work condition allowing for a clean exit of event driven apps without the headache of carefully chaining together exit callbacks
// Applications that aren't designed around an event driven model should set callerOwns to true to keep the around during global work exhaustion
virtual void SetWorkerIdIsThreadRunner(WorkerId_t, bool runner) = 0;
virtual Threading::Threads::ThreadShared_t ResolveHandle(WorkerId_t) = 0;
virtual AuBST<ThreadGroup_t, AuList<ThreadId_t>> GetThreads() = 0;
virtual WorkerId_t GetCurrentThread() = 0;
// Synchronization
// Note: syncing to yourself will nullify requireSignal to prevent deadlock
virtual bool Sync(WorkerId_t group, AuUInt32 timeoutMs = 0, bool requireSignal = false) = 0;
virtual void Signal(WorkerId_t group) = 0;
virtual void SyncAllSafe() = 0;
// Features
virtual void AddFeature(WorkerId_t id, AuSPtr<Threading::Threads::IThreadFeature> feature, bool async = false) = 0;
// Debug
virtual void AssertInThreadGroup(ThreadGroup_t thread) = 0;
virtual void AssertWorker(WorkerId_t id) = 0;
virtual bool Poll(bool block) = 0;
virtual bool ScheduleLoopSource(const AuSPtr<Loop::ILoopSource> &loopSource, WorkerId_t workerId, AuUInt32 timeout, const AuConsumer<AuSPtr<Loop::ILoopSource>, bool> &callback) = 0;
};
// TODO: move the following trash out of here
#pragma region EASE_OF_READING
struct BasicWorkStdFunc : IWorkItemHandler
{
std::function<void()> callback;
std::function<void()> shutdown; // error
AuFunction<void()> callback;
AuFunction<void()> shutdown; // error
BasicWorkStdFunc(std::function<void()> &&callback, std::function<void()> &&shutdown) : callback(std::move(callback)), shutdown(std::move(shutdown))
BasicWorkStdFunc(AuFunction<void()> &&callback, AuFunction<void()> &&shutdown) : callback(std::move(callback)), shutdown(std::move(shutdown))
{}
BasicWorkStdFunc(std::function<void()> &&callback) : callback(std::move(callback))
BasicWorkStdFunc(AuFunction<void()> &&callback) : callback(std::move(callback))
{}
BasicWorkStdFunc(const std::function<void()> &callback) : callback(callback)
BasicWorkStdFunc(const AuFunction<void()> &callback) : callback(callback)
{}
BasicWorkStdFunc(const std::function<void()> &callback, const std::function<void()> &shutdown) : callback(callback), shutdown(shutdown)
BasicWorkStdFunc(const AuFunction<void()> &callback, const AuFunction<void()> &shutdown) : callback(callback), shutdown(shutdown)
{}
private:
@ -315,7 +62,7 @@ namespace Aurora::Async
try
{
callback();
}
}
catch (...)
{
Debug::PrintError();
@ -330,7 +77,7 @@ namespace Aurora::Async
{
shutdown();
}
}
}
catch (...)
{
Debug::PrintError();
@ -341,214 +88,9 @@ namespace Aurora::Async
#if !defined(_CPPSHARP)
/// @hideinitializer
struct BasicWorkCtx : WorkPriv
{
BasicWorkCtx()
{
magic = AuConvertMagicTag32("BWOT");
opt = nullptr;
}
void *opt;
};
/// @hideinitializer
template<typename Info_t = AVoid, typename Result_t = AVoid, typename Task_t = FTask<Info_t, Result_t>, typename Job_t = FJob<Info_t, Result_t>>
struct BasicWorkCallback : IWorkItemHandler, std::enable_shared_from_this<IWorkItemHandler>
{
BasicWorkCallback()
{
caller = GetAsyncApp()->GetCurrentThread();
}
BasicWorkCallback(Task_t &&task) : task(std::move(task))
{
caller = GetAsyncApp()->GetCurrentThread();
}
BasicWorkCallback(Task_t &&task, Job_t &&callback) : task(std::move(task)), callback(std::move(callback))
{
caller = GetAsyncApp()->GetCurrentThread();
}
BasicWorkCallback(const Task_t &task) : task(task)
{
caller = GetAsyncApp()->GetCurrentThread();
}
BasicWorkCallback(const Task_t &task, const Job_t &callback) : task(task), callback(callback)
{
caller = GetAsyncApp()->GetCurrentThread();
}
BasicWorkCallback(const Task_t &task, const Job_t &callback, const Info_t &info) : task(task), callback(callback), input(info)
{
caller = GetAsyncApp()->GetCurrentThread();
}
BasicWorkCallback(Task_t &&task, const Job_t &callback, const Info_t &info) : task(std::move(task)), callback(callback), input(info)
{
caller = GetAsyncApp()->GetCurrentThread();
}
BasicWorkCallback(Task_t &&task, Job_t &&callback, const Info_t &info) : task(std::move(task)), callback(std::move(callback)), input(info)
{
caller = GetAsyncApp()->GetCurrentThread();
}
BasicWorkCallback(Task_t &&task, Job_t &&callback, Info_t &&info) : task(std::move(task)), callback(std::move(callback)), input(std::move(info))
{
caller = GetAsyncApp()->GetCurrentThread();
}
BasicWorkCallback(const Task_t &task, const Job_t &callback, Info_t &&info) : task(task), callback(callback), input(info)
{
caller = GetAsyncApp()->GetCurrentThread();
}
Info_t input;
Task_t task;
Job_t callback;
BasicWorkCallback<Info_t, Result_t, Task_t, Job_t> &SetTask(const Task_t &task)
{
this->task = task;
return *this;
}
BasicWorkCallback<Info_t, Result_t, Task_t, Job_t> &SetTask(const Job_t &callback)
{
this->callback = callback;
return *this;
}
private:
static constexpr bool IsCallbackPtr = std::is_pointer_v<Job_t> || AuIsBaseOfTemplate<std::shared_ptr, Job_t>::value;
static constexpr bool IsTaskPtr = std::is_pointer_v<Task_t> || AuIsBaseOfTemplate<std::shared_ptr, Task_t>::value;
WorkerId_t caller;
BasicWorkCtx secretContext_;
Result_t resultValue_;
virtual void *GetPrivateData() override { return &secretContext_; }
void DispatchFrame(ProcessInfo &info) override
{
try
{
if constexpr (IsTaskPtr)
{
resultValue_ = task->onFrame(input);
}
else
{
resultValue_ = task.onFrame(input);
}
}
catch (...)
{
Debug::PrintError();
Shutdown();
return;
}
auto pin = std::static_pointer_cast<std::remove_pointer_t<decltype(this)>>(this->shared_from_this());
std::function<void()> func = [pin]()
{
try
{
pin->secretContext_.opt = &pin->resultValue_;
if constexpr (IsCallbackPtr)
{
pin->callback->onSuccess(pin->input, pin->resultValue_);
}
else
{
if (pin->callback.onSuccess)
{
pin->callback.onSuccess(pin->input,pin->resultValue_);
}
}
}
catch (...)
{
Debug::PrintError();
}
};
try
{
if (caller == GetAsyncApp()->GetCurrentThread())
{
func();
}
else
{
std::function<void()> err = [pin]()
{
pin->CallOnFailure();
};
// TODO: this is somewhat evil. double alloc when we could reuse this
if (!NewWorkItem(caller, AuMakeShared<BasicWorkStdFunc>(func, err))->Dispatch())
{
pin->CallOnFailure();
}
}
}
catch (...)
{
Debug::PrintError();
Shutdown();
}
}
void Shutdown() override
{
try
{
CallOnFailure();
}
catch (...)
{
Debug::PrintError();
}
}
void CallOnFailure()
{
if constexpr (IsCallbackPtr)
{
if constexpr (AuIsBaseOfTemplate<std::function, decltype(callback->onFailure)>::value)
{
if (!callback->onFailure)
{
return;
}
}
callback->onFailure(input);
}
else
{
if constexpr (AuIsBaseOfTemplate<std::function, decltype(callback.onFailure)>::value)
{
if (!callback.onFailure)
{
return;
}
}
callback.onFailure(input);
}
}
};
/// @hideinitializer
template<typename Frame_t = std::function<void()>, typename Cleanup_t = std::function<void()>>
template<typename Frame_t = AuFunction<void()>, typename Cleanup_t = AuFunction<void()>>
struct WorkItemCallable : IWorkItemHandler
{
Frame_t frame;
@ -581,12 +123,13 @@ namespace Aurora::Async
cleanup();
}
};
#define ASYNC_ERROR(exp) { if constexpr (std::is_same_v<T, bool>) { SysPushErrorGen(exp); return {}; } else { throw std::string(exp); } }
#define ASYNC_FINISH { if constexpr (std::is_same_v<T, bool>) { return true; } }
template<typename T = void, typename... Args, AU_TEMPLATE_ENABLE_WHEN(std::is_same_v<T, bool> || std::is_void<T>::value)>
static std::function<T(Args&&...)> TranslateAsyncFunctionToDispatcherWithThread(WorkerId_t id, std::function<void(Args...)> func)
static AuFunction<T(Args&&...)> TranslateAsyncFunctionToDispatcherWithThread(WorkerId_t id, AuFunction<void(Args...)> func)
{
if (!func) return {};
return [=](Args&&... in) -> T
@ -594,26 +137,28 @@ namespace Aurora::Async
auto work = AuMakeShared<BasicWorkStdFunc>([=]() -> void {
func(in...);
});
if (!work) ASYNC_ERROR("can't dispatch async call; out of memory");
if (!work) ASYNC_ERROR("can't dispatch async call. out of memory");
auto workItem = NewWorkItem(id, work);
if (!workItem) ASYNC_ERROR("can't dispatch async call; out of memory");
if (!workItem) ASYNC_ERROR("can't dispatch async call. out of memory");
workItem->Dispatch();
ASYNC_FINISH;
};
}
/// Async app only
template<typename T = void, typename... Args, AU_TEMPLATE_ENABLE_WHEN(std::is_same_v<T, bool> || std::is_void<T>::value)>
static std::function<T(Args&&...)> TranslateAsyncFunctionToDispatcher(std::function<void(Args...)> func)
static AuFunction<T(Args&&...)> TranslateAsyncFunctionToDispatcher(AuFunction<void(Args...)> func)
{
return TranslateAsyncFunctionToDispatcherWithThread(GetAsyncApp()->GetCurrentThread(), func);
}
/// Async app only
template<typename B = void, typename T, typename... Args, AU_TEMPLATE_ENABLE_WHEN(std::is_same_v<T, bool> || std::is_void<T>::value)>
static std::function<T(std::function<void(const B&)>, Args...)> TranslateAsyncReturnableFunctionToDispatcherWithThread(WorkerId_t id, std::function<B(Args...)> func)
static AuFunction<T(AuFunction<void(const B&)>, Args...)> TranslateAsyncReturnableFunctionToDispatcherWithThread(WorkerId_t id, AuFunction<B(Args...)> func)
{
return [=](std::function<T(const B&)> callback, Args... in) -> T
return [=](AuFunction<T(const B&)> callback, Args... in) -> T
{
auto work = AuMakeShared<BasicWorkCallback<AVoid, B>>();
auto work = AuMakeShared<WorkPairImpl<AVoid, B>>();
if (!work) ASYNC_ERROR("can't dispatch async call; out of memory");
work.task.onProcess = [=](const AVoid &) -> B
{
@ -631,69 +176,6 @@ namespace Aurora::Async
};
}
template<typename Info_t = AVoid, typename Result_t = AVoid, typename Task_t = FTask<Info_t, Result_t>, typename Job_t = FJob<Info_t, Result_t>>
static AuSPtr<IWorkItem> NewBasicWorkCallback(const WorkerId_t &worker, const Task_t &task, const Job_t &job, bool enableWait = false)
{
return NewWorkItem(worker, AuMakeShared<BasicWorkCallback<Info_t, Result_t, Task_t>>(std::move(task), job), enableWait);
}
template<typename Info_t = AVoid, typename Result_t = AVoid, typename Task_t = FTask<Info_t, Result_t>, typename Job_t = FJob<Info_t, Result_t>>
static AuSPtr<IWorkItem> DispatchBasicWorkCallback(const WorkerId_t &worker, const Task_t &task, const Job_t &job, bool enableWait = false)
{
return NewBasicWorkCallback<Info_t, Result_t, Task_t, Job_t>(worker, task, job, enableWait)->Dispatch();
}
template<typename Info_t = AVoid, typename Result_t = AVoid, typename Task_t = FTask<Info_t, Result_t>, typename Job_t = FJob<Info_t, Result_t>>
static AuSPtr<IWorkItem> NewBasicWorkCallback(const WorkerId_t &worker, Task_t &&task, const Job_t &job, bool enableWait = false)
{
return NewWorkItem(worker, AuMakeShared<BasicWorkCallback<Info_t, Result_t, Task_t>>(std::move(task), job), enableWait);
}
template<typename Info_t = AVoid, typename Result_t = AVoid, typename Task_t = FTask<Info_t, Result_t>, typename Job_t = FJob<Info_t, Result_t>>
static AuSPtr<IWorkItem> DispatchBasicWorkCallback(const WorkerId_t &worker, Task_t &&task, const Job_t &job, bool enableWait = false)
{
return NewBasicWorkCallback<Info_t, Result_t, Task_t, Job_t>(worker, std::move(task), job, enableWait)->Dispatch();
}
template<typename Info_t = AVoid, typename Result_t = AVoid, typename Task_t = FTask<Info_t, Result_t>, typename Job_t = FJob<Info_t, Result_t>>
static AuSPtr<IWorkItem> NewBasicWorkCallback(const WorkerId_t &worker, Task_t &&task, Job_t &&job, bool enableWait = false)
{
return NewWorkItem(worker, AuMakeShared<BasicWorkCallback<Info_t, Result_t, Task_t>>(std::move(task), std::move(job)), enableWait);
}
template<typename Info_t = AVoid, typename Result_t = AVoid, typename Task_t = FTask<Info_t, Result_t>, typename Job_t = FJob<Info_t, Result_t>>
static AuSPtr<IWorkItem> DispatchBasicWorkCallback(const WorkerId_t &worker, Task_t &&task, Job_t &&job, bool enableWait = false)
{
return NewBasicWorkCallback<Info_t, Result_t, Task_t, Job_t>(worker, std::move(task), std::move(job), enableWait)->Dispatch();
}
template<typename Info_t = AVoid, typename Result_t = AVoid, typename Task_t = FTask<Info_t, Result_t>, typename Job_t = FJob<Info_t, Result_t>>
static AuSPtr<IWorkItem> DispatchBasicWorkCallback(const WorkerId_t &worker, const Task_t &task, const Job_t &job, const Info_t &inputParameters, bool enableWait = false)
{
// TOOD: use faster object if job parguments are invalid
// It would be nice if we didn't have to drag the job callback pair around with us
return NewWorkItem(worker, AuMakeShared<BasicWorkCallback<Info_t, Result_t, Task_t>>(task, job, inputParameters), enableWait)->Dispatch();
}
template<typename Info_t = AVoid, typename Result_t = AVoid, typename Task_t = FTask<Info_t, Result_t>, typename Job_t = FJob<Info_t, Result_t>, typename ClazzImpl>
AuSPtr<IWorkItem> DispatchFunctional(const WorkerId_t &worker, ClazzImpl task, const Job_t &job, const Info_t &inputParameters, bool enableWait = false)
{
return DispatchBasicWorkCallback<Info_t, Result_t, Task_t, Job_t>(worker, TaskFromConsumerRefT<Info_t, Result_t>(task), job, inputParameters, enableWait);
}
template<typename Info_t = AVoid, typename Result_t = AVoid, typename Task_t = FTask<Info_t, Result_t>, typename Job_t = FJob<Info_t, Result_t>, typename ClazzImpl>
AuSPtr<IWorkItem> DispatchFunctor(const WorkerId_t &worker, ClazzImpl task, const Job_t &job, const Info_t &inputParameters, bool enableWait = false)
{
return DispatchBasicWorkCallback<Info_t, Result_t, Task_t, Job_t>(worker, TaskFromConsumerRefT<Info_t, Result_t>(task), job, inputParameters, enableWait);
}
template<typename Info_t = AVoid, typename Result_t = AVoid, typename Task_t = FTask<Info_t, Result_t>, typename Job_t = FJob<Info_t, Result_t>, typename ClazzImpl>
AuSPtr<IWorkItem> DispatchVoid(const WorkerId_t &worker, ClazzImpl task, const Job_t &job, const Info_t &inputParameters, bool enableWait = false)
{
return DispatchBasicWorkCallback<Info_t, Result_t, Task_t, Job_t>(worker, TaskFromVoidVoid<Info_t, Result_t>(task), job, inputParameters, enableWait);
}
#undef ASYNC_ERROR
#undef ASYNC_FINISH
@ -701,3 +183,11 @@ namespace Aurora::Async
#pragma endregion EASE_OF_READING
}
#if !defined(_CPPSHARP)
#include "JobFrom.hpp"
#include "TaskFrom.hpp"
#include "WorkPairImpl.hpp"
#include "WorkBasic.hpp"
#endif

View File

@ -0,0 +1,75 @@
/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: AsyncTypes.hpp
Date: 2021-11-1
Author: Reece
***/
#pragma once
namespace Aurora::Loop
{
class ILoopSource;
}
namespace Aurora::Async
{
struct IWorkItem;
struct IAsyncApp;
struct IThreadPool;
struct AVoid
{
AuUInt8 x[1];
};
/// IAsyncApp:
/// ThreadGroup_t:
/// 0 = system main thread
/// 1+ = user defined | user allocated thread
using ThreadGroup_t = AuUInt8;
/// ThreadId_t:
/// -1 = invalid or any
/// index = tid/runner id
using ThreadId_t = AuUInt16;
static const ThreadId_t kThreadIdAny = -1;
struct WorkerId_t : AuPair<ThreadGroup_t, ThreadId_t>
{
WorkerId_t() : AuPair<ThreadGroup_t, ThreadId_t>(0, 0)
{}
WorkerId_t(ThreadGroup_t group) : AuPair<ThreadGroup_t, ThreadId_t>(group, kThreadIdAny)
{}
WorkerId_t(ThreadGroup_t group, ThreadId_t id) : AuPair<ThreadGroup_t, ThreadId_t>(group, id)
{}
WorkerId_t(const WorkerId_t &cpy) : AuPair<ThreadGroup_t, ThreadId_t>(cpy.first, cpy.second)
{}
};
struct WorkPriv
{
AuUInt32 magic;
};
struct WorkerPId_t : WorkerId_t
{
WorkerPId_t()
{}
WorkerPId_t(const AuSPtr<IThreadPool> &pool, ThreadGroup_t group) : WorkerId_t(group, kThreadIdAny), pool(pool)
{}
WorkerPId_t(const AuSPtr<IThreadPool> &pool, ThreadGroup_t group, ThreadId_t id) : WorkerId_t(group, id), pool(pool)
{}
WorkerPId_t(const AuSPtr<IThreadPool> &pool, const WorkerId_t &cpy) : WorkerId_t(cpy.first, cpy.second), pool(pool)
{}
AuSPtr<IThreadPool> pool;
};
}

View File

@ -0,0 +1,25 @@
/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: IAsyncApp.hpp
Date: 2021-11-1
Author: Reece
***/
#pragma once
namespace Aurora::Async
{
struct IAsyncApp : IThreadPool
{
// Main thread logic
/// Initializes the async application before the hand-off
virtual void Start() = 0;
/// Hand off
virtual void Main() = 0;
/// Console config
virtual void SetConsoleCommandDispatcher(WorkerId_t id) = 0;
};
}

View File

@ -0,0 +1,70 @@
/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: IThreadPool.hpp
Date: 2021-11-1
Author: Reece
***/
#pragma once
namespace Aurora::Async
{
struct IThreadPool
{
// Spawning
/// @deprecated
virtual bool Spawn(WorkerId_t workerId) = 0;
// Event runner threads release the entire application upon encountering a zero work condition allowing for a clean exit of event driven apps without the headache of carefully chaining together exit callbacks
// Applications that aren't designed around an event driven model should set runner to false to keep the around during global work exhaustion
// tl'dr: runner = true, async app; runner = false, thread pool
virtual void SetRunningMode(bool eventRunning) = 0;
// Converts the current thread into an async thread with runner = false
// Use with RunOnce, Poll
virtual bool Create(WorkerId_t workerId) = 0;
//virtual bool CreateAndRun(WorkerId_t workerId) = 0;
// @returns any threads with runner = true
virtual bool InRunnerMode() = 0;
// Manual execution (system thread and ::Create() users)
virtual bool Poll() = 0;
virtual bool RunOnce() = 0;
virtual bool Run() = 0;
//
virtual void Shutdown() = 0;
virtual bool Exiting() = 0;
//
virtual AuSPtr<IWorkItem> NewWorkItem(const WorkerId_t &worker, const AuSPtr<IWorkItemHandler> &task, bool supportsBlocking = false) = 0;
virtual AuSPtr<IWorkItem> NewFence() = 0;
//
virtual Threading::Threads::ThreadShared_t ResolveHandle(WorkerId_t) = 0;
//
virtual AuBST<ThreadGroup_t, AuList<ThreadId_t>> GetThreads() = 0;
//
virtual WorkerId_t GetCurrentThread() = 0;
// Synchronization
// Note: syncing to yourself will nullify requireSignal to prevent deadlock
virtual bool Sync(WorkerId_t workerId, AuUInt32 timeoutMs = 0, bool requireSignal = false) = 0;
virtual void Signal(WorkerId_t workerId) = 0;
virtual void SyncAllSafe() = 0;
// Features
virtual void AddFeature(WorkerId_t id, AuSPtr<Threading::Threads::IThreadFeature> feature, bool async = false) = 0;
// Debug
virtual void AssertInThreadGroup(ThreadGroup_t group) = 0;
virtual void AssertWorker(WorkerId_t id) = 0;
// Async subsystem glue
virtual bool ScheduleLoopSource(const AuSPtr<Loop::ILoopSource> &loopSource, WorkerId_t workerId, AuUInt32 timeout, const AuConsumer<AuSPtr<Loop::ILoopSource>, bool> &callback) = 0;
};
}

View File

@ -0,0 +1,54 @@
/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: IWorkItem.hpp
Date: 2021-11-1
Author: Reece
***/
#pragma once
namespace Aurora::Async
{
struct IWorkItem
{
virtual AuSPtr<IWorkItem> WaitFor(const AuSPtr<IWorkItem> &workItem) = 0;
virtual AuSPtr<IWorkItem> WaitFor(const AuList<AuSPtr<IWorkItem>> &workItem) = 0;
// ms = time relative to the current time
virtual AuSPtr<IWorkItem> SetSchedTime(AuUInt32 ms) = 0;
// ns = Aurora::Time::CurrentClockMS() + relativeMs
virtual AuSPtr<IWorkItem> SetSchedTimeAbs(AuUInt32 ms) = 0;
// ns = time relative to the current time
virtual AuSPtr<IWorkItem> SetSchedTimeNs(AuUInt64 ns) = 0;
// ns = Aurora::Time::CurrentClockNS() + relativeNs
virtual AuSPtr<IWorkItem> SetSchedTimeNsAbs(AuUInt64 ns) = 0;
// ms = time relative to the time at which the work item would otherwise dispatch
virtual AuSPtr<IWorkItem> AddDelayTime(AuUInt32 ms) = 0;
// ns = time relative to the time at which the work item would otherwise dispatch
virtual AuSPtr<IWorkItem> AddDelayTimeNs(AuUInt64 ns) = 0;
virtual AuSPtr<IWorkItem> Then(const AuSPtr<IWorkItem> &next) = 0;
virtual AuSPtr<IWorkItem> Dispatch() = 0;
// per work frame, available work is dequed and sorted.
// work is sorted by prio and then by initial submission index.
//
// prios under .25 will yield for new work at time of possible dispatch
// only if there are work entries of > .5 in the pending work queue.
virtual void SetPrio(float val = 0.5f) = 0;
virtual bool BlockUntilComplete() = 0;
virtual bool HasFinished() = 0;
virtual bool HasFailed() = 0;
virtual void Cancel() = 0;
virtual void *GetPrivateData() = 0;
virtual AuOptional<void *> ToWorkResultT() = 0;
};
}

View File

@ -0,0 +1,47 @@
/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: IWorkItemHandler.hpp
Date: 2021-11-1
Author: Reece
***/
#pragma once
namespace Aurora::Async
{
struct IWorkItemHandler
{
enum class EProcessNext
{
eInvalid = -1,
eFinished = 0,
eRerun,
eSchedule,
eFailed
};
struct ProcessInfo
{
ProcessInfo(bool finished) : type(finished ? EProcessNext::eFinished : EProcessNext::eFailed) {}
ProcessInfo(EProcessNext type) : type(type) {}
ProcessInfo(const AuList<AuSPtr<IWorkItem>> &blockedBy) : type(EProcessNext::eSchedule), waitFor(blockedBy) {}
// ...
EProcessNext type;
AuList<AuSPtr<IWorkItem>> waitFor;
AuUInt32 reschedMs;
AuUInt64 reschedNs;
// @hideinitializer
IThreadPool *pool;
};
virtual void DispatchFrame(ProcessInfo &info) = 0;
/// A really terrible name for the overloadable method that serves as the critical failure callback
/// You have a 'shutdown'/free function you can overload, it's called the dtor
/// Don't moan about the shitty naming of this, im not refactoring it
virtual void Shutdown() = 0;
virtual void *GetPrivateData() { return nullptr; }
};
}

View File

@ -0,0 +1,186 @@
/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: JobFrom.hpp
Date: 2021-11-1
Author: Reece
***/
#pragma once
namespace Aurora::Async
{
template<class Info_t = AVoid, class Result_t = AVoid, class Callable_t>
static inline FJob<Info_t, Result_t> JobFromPairConsumer(const /*AuConsumer<const Info_t &, const Result_t &> */ Callable_t&onSuccess)
{
FJob<Info_t, Result_t> ret;
ret.onSuccess = [=](const Info_t &in, const Result_t &a)
{
onSuccess(in, a);
};
return ret;
}
template<class Info_t = AVoid, class Result_t = AVoid, class Callable_t>
static inline FJob<Info_t, Result_t> JobFromResultConsumer(/*AuConsumer<const Result_t &> */ Callable_t&&onSuccess)
{
FJob<Info_t, Result_t> ret;
ret.onSuccess = [=](const Info_t &in, const Result_t &a)
{
onSuccess(a);
};
return ret;
}
template<typename Out_t = AVoid, typename ... Args, typename Callable_t>
FJob<std::tuple<Args...>, Out_t> JobFromTupleConsumer(/*AuConsumer<Args..., const Result_t &> */ Callable_t &&onSuccess)
{
FJob<std::tuple<Args...>, Out_t> ret;
static_assert(std::is_same_v<std::tuple<Args...>, std::tuple<AuUInt64, AuUInt64>>);
ret.onSuccess = [=](const std::tuple<Args...> &in, const Out_t &a)
{
std::apply(onSuccess, std::tuple_cat(in, std::make_tuple<const Out_t &>(a)));
};
return ret;
}
template<typename Out_t = AVoid, typename ... Args, typename Callable_t, typename FailureCallable_t>
FJob<std::tuple<Args...>, Out_t> JobFromTupleConsumerEx(/*AuConsumer<Args..., const Result_t &> */ Callable_t &&onSuccess, FailureCallable_t &&onFailure)
{
FJob<std::tuple<Args...>, Out_t> ret;
static_assert(std::is_same_v<std::tuple<Args...>, std::tuple<AuUInt64, AuUInt64>>);
ret.onSuccess = [=](const std::tuple<Args...> &in, const Out_t &a)
{
std::apply(onSuccess, std::tuple_cat(in, std::make_tuple<const Out_t &>(a)));
};
ret.onFailure = [=](const std::tuple<Args...> &in)
{
std::apply(onFailure, in);
};
return ret;
}
template<class Info_t = AVoid, class Result_t = AVoid>
static inline FJob<Info_t, Result_t> JobFromPairConsumerEx(const AuConsumer<const Info_t &, const Result_t &> &onSuccess, const AuConsumer<const Info_t &> &onFailure)
{
FJob<Info_t, Result_t> ret;
ret.onSuccess = [=](const Info_t &in, const Result_t &a)
{
onSuccess(in, a);
};
ret.onFailure = [=](const Info_t &a)
{
onFailure(a);
};
return ret;
}
template<class Info_t = AVoid, class Result_t = AVoid>
static inline FJob<Info_t, Result_t> JobFromConsumer(const AuConsumer<const Result_t &> &onSuccess, const AuVoidFunc &onFailure)
{
FJob<Info_t, Result_t> ret;
ret.onSuccess = [=](const Info_t &in, const Result_t &a)
{
onSuccess(a);
};
ret.onFailure = [=](const Info_t &a)
{
onFailure();
};
return ret;
}
template<class Info_t = AVoid, class Result_t = AVoid, class BaseInfo_t = AVoid, class BaseResult_t = AVoid>
static inline FJob<Info_t, Result_t> JobFromDerivedJob(const FJob<BaseInfo_t, BaseResult_t> &reference)
{
FJob<Info_t, Result_t> ret;
ret.onSuccess = [=](const Info_t &in, const Result_t &a)
{
if (reference.onSuccess)
{
reference.onSuccess(in, a);
}
};
ret.onFailure = [=](const Info_t &a)
{
if (reference.onFailure)
{
reference.onFailure(a);
}
};
return ret;
}
template<typename ReturnValue_t = void, typename Arg0_t, typename ... Args>
static inline FJob<std::tuple<Arg0_t, Args...>, ReturnValue_t> JobFromTupleClazz(const AuConsumer<const Arg0_t &, const ReturnValue_t &> &onSuccess)
{
FJob<std::tuple<Arg0_t, Args...>, ReturnValue_t> ret;
ret.onSuccess = [=](const std::tuple<Arg0_t, Args...> &in, const ReturnValue_t &out)
{
onSuccess(std::get<0>(in), out);
};
return ret;
}
template<typename ReturnValue_t = void, typename Arg0_t, typename ... Args>
static inline FJob<std::tuple<Arg0_t, Args...>, ReturnValue_t> JobFromTupleClazzEx(const AuConsumer<const Arg0_t &, const ReturnValue_t &> &onSuccess, const AuConsumer<const Arg0_t &> &onFailure)
{
FJob<std::tuple<Arg0_t, Args...>, ReturnValue_t> ret;
ret.onSuccess = [=](const std::tuple<Arg0_t, Args...> &in, const ReturnValue_t &out)
{
onSuccess(std::get<0>(in), out);
};
ret.onFailure = [=](const std::tuple<Arg0_t, Args...> &in)
{
onFailure(std::get<0>(in));
};
return ret;
}
#if 0
template<typename ReturnValue_t = void, typename Clazz_t, typename ... Args>
static inline FJob<std::tuple<AuSPtr<Clazz_t>, Args...>, ReturnValue_t> JobFromTupleClazz(const AuConsumer<const ReturnValue_t &> &onSuccess)
{
FJob<std::tuple<AuSPtr<Clazz_t>, Args...>, ReturnValue_t> ret;
ret.onSuccess = [=](const std::tuple<AuSPtr<Clazz_t>, Args...> &in, const ReturnValue_t &out)
{
onSuccess(out);
};
return ret;
}
template<typename ReturnValue_t = void, typename Clazz_t, typename ... Args>
static inline FJob<std::tuple<AuSPtr<Clazz_t>, Args...>, ReturnValue_t> JobFromTupleClazz(const AuConsumer<Args..., const ReturnValue_t &> &onSuccess)
{
FJob<std::tuple<AuSPtr<Clazz_t>, Args...>, ReturnValue_t> ret;
ret.onSuccess = [=](const std::tuple<AuSPtr<Clazz_t>, Args...> &in, const ReturnValue_t &out)
{
std::apply(onSuccess, std::tuple_cat(AuTuplePopFront(in), std::make_tuple<const ReturnValue_t &>(out)));
};
return ret;
}
#endif
template<typename ReturnValue_t = void, typename ... Args>
static inline FJob<std::tuple<Args...>, ReturnValue_t> JobFromTupleResultConsumer(const AuConsumer<const ReturnValue_t &> &onSuccess)
{
FJob<std::tuple<Args...>, ReturnValue_t> ret;
ret.onSuccess = [=](const std::tuple<Args...> &in, const ReturnValue_t &out)
{
onSuccess(out);
};
return ret;
}
template<typename ReturnValue_t = void, typename ... Args>
static inline FJob<std::tuple<Args...>, ReturnValue_t> JobFromTuple(const AuConsumer<Args..., const ReturnValue_t &> &onSuccess)
{
FJob<std::tuple<Args...>, ReturnValue_t> ret;
ret.onSuccess = [=](const std::tuple<Args...> &in, const ReturnValue_t &out)
{
std::apply(onSuccess, std::tuple_cat(in, std::make_tuple<const ReturnValue_t &>(out)));
};
return ret;
}
}

View File

@ -0,0 +1,33 @@
/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: Jobs.hpp
Date: 2021-11-1
Author: Reece
***/
#pragma once
namespace Aurora::Async
{
template<class Info_t = AVoid, class Result_t = AVoid>
struct FJob
{
using InfoType_t = Info_t;
using ResultType_t = Result_t;
AuFunction<void(const Info_t &, const Result_t &)> onSuccess;
AuFunction<void(const Info_t &)> onFailure;
};
using FVoidJob = FJob<AVoid, AVoid>;
template<class Info_t = AVoid, class Result_t = AVoid>
struct CJob
{
using InfoType_t = Info_t;
using ResultType_t = Result_t;
void(* onSuccess)(const Info_t &, const Result_t &);
void(* onFailure)(const Info_t &);
};
}

View File

@ -0,0 +1,87 @@