diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index a3b32549..2421d184 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -14,6 +14,12 @@ jobs: uses: actions/checkout@v4 with: submodules: recursive + - name: Checkout deps/ldeps + uses: actions/checkout@v4 + with: + repository: lnd3/ldeps + path: deps/ldeps + submodules: recursive - name: Create Build Environment run: cmake -E make_directory ${{github.workspace}}/build - name: Configure CMake @@ -35,6 +41,12 @@ jobs: uses: actions/checkout@v4 with: submodules: recursive + - name: Checkout deps/ldeps + uses: actions/checkout@v4 + with: + repository: lnd3/ldeps + path: deps/ldeps + submodules: recursive - name: Install Dependencies run: | sudo apt-get update diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..1bff7db7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,41 @@ +# ltools project + +## What This Is +`ltools` is a modern C++ library consisting of a collection of packages and tools for modern C++ development, organized around the build system `bs` from `ldeps`. Packages found at `packages/**`. + +## Notable Packages +- **nodegraph**: Package `nodegraph`. Location `packages/nodegraph`. Node graph system. `NodeGraphOp` base class, `NodeGraphOpCached` for cached operations, inputs/outputs, cache blocks, `NodeGraphSchema` with Load/Save/JSON serialization and node factory. Includes trading-specific operations (data IO, detectors, filters, indicators). Namespace: `l::nodegraph`. +- **ecs**: Package `ecs`. Location `packages/ecs`. Entity component system. Entity contains components. Components contain data structures. Iterate over component groups in systems and do work on components and/or entities. Access any data by name or id. Namespace: `l::ecs`. +- **rendering**: Package `rendering`. Location `packages/rendering`. UI and rendering via ImGui/ImPlot. Namespace: `l::ui`. +- **serialization**: Package `serialization`. Location `packages/serialization`. JSON serialization framework (`JsonSerializationBase`, `JsonBuilder`, `JsonValue`). Used by `NodeGraphSchema` for persistence. Namespace: `l::serialization`. +- **math**: Package `math`. Location `packages/math`. Math utilities, tweening, algorithms. Namespace: `l::math`. +- **network**: Package `network`. Location `packages/network`. Network communication. Namespace: `l::network`. +- **nn**: Package `nn`. Location `packages/nn`. Neural network utilities. Namespace: `l::nn`. +- **memory**: Package `memory`. Location `packages/memory`. Containers and memory management. Namespace: `l::container`. +- **concurrency**: Package `concurrency`. Location `packages/concurrency`. Threading and synchronization. Namespace: `l::concurrency`. +- **audio**: Package `audio`. Location `packages/audio`. Audio utilities. Namespace: `l::audio`. +- **hid**: Package `hid`. Location `packages/hid`. Human input device and MIDI. Namespace: `l::hid`. +- **physics**: Package `physics`. Location `packages/physics`. Physics simulation and octree. Namespace: `l::physics`. +- **crypto**: Package `crypto`. Location `packages/crypto`. Cryptography utilities. Namespace: `l::crypto`. +- **filesystem**: Package `filesystem`. Location `packages/filesystem`. File system utilities. Namespace: `l::filesystem`. +- **storage**: Package `storage`. Location `packages/storage`. File caching and storage. Namespace: `l::filecache`. +- **logging**: Package `logging`. Location `packages/logging`. Logging system. Namespace: `l::logging`. +- **tools**: Package `tools`. Location `packages/tools`. Signal utilities and tools. Namespace: `l::signals`. +- **meta**: Package `meta`. Location `packages/meta`. Reflection and type info. Namespace: `l::meta`. +- **testing**: Package `testing`. Location `packages/testing`. Test utilities. Namespace: `l::testing`. + +## Dependencies +- **ldeps**: Location `deps/ldeps`. See `deps/ldeps/CLAUDE.md`. + +## Coding Patterns +As required by `bs`, packages have a certain layout: `${package_name}/[include/${package_name}/|source/common|tests/common|]`. + +### Node graph +- Base class: `NodeGraphOp` in `packages/nodegraph/include/nodegraph/core/NodeGraphBase.h` +- Cached base class: `NodeGraphOpCached` — adds `ProcessWriteCached()` and `ProcessReadCached()` for buffered processing +- Schema: `NodeGraphSchema` inherits `JsonSerializationBase` and `NodeFactoryBase` — provides `Load()`, `Save()`, `RegisterNodeType()`, `NewNode()` +- Node inputs: `AddInput2("Name")` for connected, `AddInput("Name", default, size, min, max)` for params, `AddConstant("Name", default, size, min, max)` for constants +- Node outputs: `AddOutput("name")` +- Process signature: `Process(int32_t numSamples, int32_t numCacheSamples, std::vector&, std::vector&)` +- Registration: `schema.RegisterNodeType("Category", ID, "Name", "Description")` + switch case +- Node creation: `group.NewNode(id, NodeType::Default)` — NodeType required (`Default`, `ExternalInput`, `ExternalOutput`) diff --git a/CMakeLists.txt b/CMakeLists.txt index e0e72e52..25dfdd69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,13 +24,14 @@ else() filesystem meta memory + math concurrency serialization crypto - math audio hid nodegraph + nn network storage @@ -43,4 +44,52 @@ else() bs_configure_packages("packages" "${PACKAGE_NAMES}") + + option(LTOOLS_ENABLE_UNITY_BUILD "Enable unity builds for faster compilation" OFF) + if(LTOOLS_ENABLE_UNITY_BUILD) + set_target_properties(logging PROPERTIES UNITY_BUILD ON) + + set_target_properties(testing PROPERTIES UNITY_BUILD ON) + set_target_properties(filesystem PROPERTIES UNITY_BUILD ON) + set_target_properties(meta PROPERTIES UNITY_BUILD ON) + set_target_properties(memory PROPERTIES UNITY_BUILD ON) + set_target_properties(concurrency PROPERTIES UNITY_BUILD ON) + set_target_properties(serialization PROPERTIES UNITY_BUILD ON) + set_target_properties(crypto PROPERTIES UNITY_BUILD ON) + set_target_properties(math PROPERTIES UNITY_BUILD ON) + set_target_properties(audio PROPERTIES UNITY_BUILD ON) + set_target_properties(hid PROPERTIES UNITY_BUILD ON) + set_target_properties(nodegraph PROPERTIES UNITY_BUILD ON) + set_target_properties(nn PROPERTIES UNITY_BUILD ON) + + set_target_properties(network PROPERTIES UNITY_BUILD ON) + set_target_properties(storage PROPERTIES UNITY_BUILD ON) + set_target_properties(tools PROPERTIES UNITY_BUILD ON) + + set_target_properties(physics PROPERTIES UNITY_BUILD ON) + set_target_properties(ecs PROPERTIES UNITY_BUILD ON) + #set_target_properties(rendering PROPERTIES UNITY_BUILD ON) + + set_target_properties(testing_test PROPERTIES UNITY_BUILD ON) + set_target_properties(filesystem_test PROPERTIES UNITY_BUILD ON) + set_target_properties(meta_test PROPERTIES UNITY_BUILD ON) + set_target_properties(memory_test PROPERTIES UNITY_BUILD ON) + set_target_properties(concurrency_test PROPERTIES UNITY_BUILD ON) + set_target_properties(serialization_test PROPERTIES UNITY_BUILD ON) + set_target_properties(crypto_test PROPERTIES UNITY_BUILD ON) + set_target_properties(math_test PROPERTIES UNITY_BUILD ON) + set_target_properties(audio_test PROPERTIES UNITY_BUILD ON) + set_target_properties(hid_test PROPERTIES UNITY_BUILD ON) + set_target_properties(nodegraph_test PROPERTIES UNITY_BUILD ON) + + set_target_properties(network_test PROPERTIES UNITY_BUILD ON) + set_target_properties(storage_test PROPERTIES UNITY_BUILD ON) + set_target_properties(tools_test PROPERTIES UNITY_BUILD ON) + + set_target_properties(physics_test PROPERTIES UNITY_BUILD ON) + #set_target_properties(ecs_test PROPERTIES UNITY_BUILD ON) + set_target_properties(rendering_test PROPERTIES UNITY_BUILD ON) + + endif() + endif() diff --git a/deps/ldeps b/deps/ldeps deleted file mode 160000 index 92d1d022..00000000 --- a/deps/ldeps +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 92d1d02274008c44e6367e665747258e9d977b75 diff --git a/packages/audio/include/audio/PortAudio.h b/packages/audio/include/audio/PortAudio.h index e796eae2..11859511 100644 --- a/packages/audio/include/audio/PortAudio.h +++ b/packages/audio/include/audio/PortAudio.h @@ -7,7 +7,7 @@ #include #include -#include "../../include/portaudio.h" +#include namespace l::audio { diff --git a/packages/audio/source/common/PortAudio.cpp b/packages/audio/source/common/PortAudio.cpp index 6c8e4b3f..5f374832 100644 --- a/packages/audio/source/common/PortAudio.cpp +++ b/packages/audio/source/common/PortAudio.cpp @@ -73,13 +73,13 @@ namespace l::audio { auto maxDevices = Pa_GetDeviceCount(); for (int32_t index = 0; index < maxDevices; index++) { auto deviceInfo = Pa_GetDeviceInfo(index); - LOG(LogInfo) << "Audio device " << index << ": " << deviceInfo->name; + LLOG(LogInfo) << "Audio device " << index << ": " << deviceInfo->name; } mInputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */ mOutputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ if (mOutputParameters.device == paNoDevice) { - LOG(LogError) << "Error: No default output device."; + LLOG(LogError) << "Error: No default output device."; return false; } @@ -104,10 +104,10 @@ namespace l::audio { //auto inputInfo = Pa_GetDeviceInfo(mInputParameters.device); mInputParameters.suggestedLatency = latencyMs / 1000.0f; // inputInfo->defaultHighInputLatency; - LOG(LogInfo) << "Port Audio set latency ms: " << static_cast(latencyMs); + LLOG(LogInfo) << "Port Audio set latency ms: " << static_cast(latencyMs); } else { - LOG(LogInfo) << "Port Audio recommended latency ms: " << static_cast(defaultLatency * 1000.0f); + LLOG(LogInfo) << "Port Audio recommended latency ms: " << static_cast(defaultLatency * 1000.0f); mOutputParameters.suggestedLatency = defaultLatency; } mOutputParameters.hostApiSpecificStreamInfo = NULL; @@ -149,7 +149,7 @@ namespace l::audio { &mAudioStreamData); if (err != paNoError) { - LOG(LogError) << "Failed to open stream: " << err; + LLOG(LogError) << "Failed to open stream: " << err; return false; } return true; @@ -159,7 +159,7 @@ namespace l::audio { bool AudioStream::StartStream() { auto err = Pa_StartStream(mPaStream); if (err != paNoError) { - LOG(LogError) << "Failed to start stream: " << err; + LLOG(LogError) << "Failed to start stream: " << err; return false; } @@ -212,7 +212,7 @@ namespace l::audio { } auto err = Pa_CloseStream(mPaStream); if (err != paNoError) { - LOG(LogError) << "Failed to close stream: " << err; + LLOG(LogError) << "Failed to close stream: " << err; return false; } return true; @@ -237,7 +237,7 @@ namespace l::audio { bool AudioManager::Init() { auto err = Pa_Initialize(); if (err != paNoError) { - LOG(LogError) << "Failed to initialize port audio."; + LLOG(LogError) << "Failed to initialize port audio."; return false; } return true; diff --git a/packages/audio/tests/common/PortAudioTest.cpp b/packages/audio/tests/common/PortAudioTest.cpp index 426030b0..35bb0f7c 100644 --- a/packages/audio/tests/common/PortAudioTest.cpp +++ b/packages/audio/tests/common/PortAudioTest.cpp @@ -58,7 +58,7 @@ TEST(PortAudio, Setup) { if (!stream->StopStream()) { - LOG(LogError) << "Failed to stop stream"; + LLOG(LogError) << "Failed to stop stream"; } manager.CloseOutStream("speaker"); diff --git a/packages/concurrency/include/concurrency/ExecutorService.h b/packages/concurrency/include/concurrency/ExecutorService.h index 8d330929..a9e1c111 100644 --- a/packages/concurrency/include/concurrency/ExecutorService.h +++ b/packages/concurrency/include/concurrency/ExecutorService.h @@ -95,7 +95,7 @@ namespace l::concurrency { Worker(Worker&&) = default; Worker(const Worker&) = default; virtual ~Worker() override { - //LOG(LogDebug) << "Destroying " << mName; + //LLOG(LogDebug) << "Destroying " << mName; } RunnableResult run(const RunState& state) override; diff --git a/packages/concurrency/source/common/ExecutorService.cpp b/packages/concurrency/source/common/ExecutorService.cpp index 34dd8cbd..03a92216 100644 --- a/packages/concurrency/source/common/ExecutorService.cpp +++ b/packages/concurrency/source/common/ExecutorService.cpp @@ -56,7 +56,7 @@ namespace l::concurrency { } RunnableResult Runnable::run(const RunState&) { - LOG(LogInfo) << "Default run implementation"; + LLOG(LogInfo) << "Default run implementation"; return RunnableResult::SUCCESS; } @@ -85,7 +85,7 @@ namespace l::concurrency { if (mRunState.mDestructing) { return; } - if (gDebugLogging) LOG(LogDebug) << "Executor service shutdown is imminent"; + if (gDebugLogging) LLOG(LogDebug) << "Executor service shutdown is imminent"; { std::lock_guard lock(mRunnablesMutex); mRunState.mDestructing = true; @@ -96,12 +96,12 @@ namespace l::concurrency { } do { - if (gDebugLogging) LOG(LogDebug) << "Executor service notifying threads of imminent shutdown"; + if (gDebugLogging) LLOG(LogDebug) << "Executor service notifying threads of imminent shutdown"; std::this_thread::sleep_for(std::chrono::milliseconds(50)); mCondition.notify_all(); } while (!mRunState.IsShutdown()); - if (gDebugLogging) LOG(LogDebug) << "Executor service notified all waiting schedulers to exit immediately"; + if (gDebugLogging) LLOG(LogDebug) << "Executor service notified all waiting schedulers to exit immediately"; for (auto& t : mPoolThreads) { if (t.joinable()) { @@ -117,18 +117,18 @@ namespace l::concurrency { } void ExecutorService::startJobs() { - LOG(LogDebug) << "Start jobs " << mName; + LLOG(LogDebug) << "Start jobs " << mName; mRunState.mRunning = true; mCondition.notify_all(); } void ExecutorService::pauseJobs() { - LOG(LogDebug) << "Pause jobs " << mName; + LLOG(LogDebug) << "Pause jobs " << mName; mRunState.mRunning = false; } void ExecutorService::clearJobs() { - LOG(LogDebug) << "Clear jobs " << mName; + LLOG(LogDebug) << "Clear jobs " << mName; std::lock_guard lock(mRunnablesMutex); mRunState.mRunning = false; @@ -138,13 +138,13 @@ namespace l::concurrency { bool ExecutorService::queueJob(std::unique_ptr runnable) { { if (mRunState.mDestructing) { - LOG(LogWarning) << "Service is shutdown and waiting for destruction"; + LLOG(LogWarning) << "Service is shutdown and waiting for destruction"; return false; } std::lock_guard lock(mRunnablesMutex); if (mMaxQueuedJobs > 0 && mRunnables.size() > mMaxQueuedJobs) { - LOG(LogWarning) << "Too many jobs!"; + LLOG(LogWarning) << "Too many jobs!"; return false; } mRunnables.push_back(std::move(runnable)); @@ -173,16 +173,16 @@ namespace l::concurrency { } std::unique_ptr runnable = nullptr; - if (gDebugLogging) LOG(LogDebug) << "Scheduler " << id << " started"; + if (gDebugLogging) LLOG(LogDebug) << "Scheduler " << id << " started"; if (!mRunState.mRunning) { std::unique_lock lock(mRunnablesMutex); - if (gDebugLogging) LOG(LogDebug) << "Scheduler " << id << " is paused"; + if (gDebugLogging) LLOG(LogDebug) << "Scheduler " << id << " is paused"; mCondition.wait(lock); } else { std::unique_lock lock(mRunnablesMutex); if (mRunnables.empty()) { - if (gDebugLogging) LOG(LogDebug) << "Scheduler " << id << " is waiting for work"; + if (gDebugLogging) LLOG(LogDebug) << "Scheduler " << id << " is waiting for work"; mCondition.wait(lock); } else { @@ -194,10 +194,10 @@ namespace l::concurrency { if (gDebugLogging) { if (runnable->NumTries() > 0) { - LOG(LogDebug) << "Scheduler " << id << " picked up requeued(" << runnable->NumTries() << ") job"; + LLOG(LogDebug) << "Scheduler " << id << " picked up requeued(" << runnable->NumTries() << ") job"; } else { - LOG(LogDebug) << "Scheduler " << id << " picked up new job"; + LLOG(LogDebug) << "Scheduler " << id << " picked up new job"; } } break; @@ -206,50 +206,50 @@ namespace l::concurrency { lock.unlock(); if (!runnable) { - if (gDebugLogging) LOG(LogDebug) << "Scheduler " << id << " sleeping"; + if (gDebugLogging) LLOG(LogDebug) << "Scheduler " << id << " sleeping"; std::this_thread::sleep_for(std::chrono::milliseconds(50)); } } } if (runnable) { - if (gDebugLogging) LOG(LogDebug) << "Scheduler " << id << " executes task"; + if (gDebugLogging) LLOG(LogDebug) << "Scheduler " << id << " executes task"; mRunState.mNumRunningJobs++; RunnableResult result = runnable->run(mRunState); mRunState.mNumRunningJobs--; switch (result) { case l::concurrency::RunnableResult::FAILURE: - if (gDebugLogging) LOG(LogDebug) << "Scheduler " << id << " task failed"; + if (gDebugLogging) LLOG(LogDebug) << "Scheduler " << id << " task failed"; runnable.reset(); break; case l::concurrency::RunnableResult::CANCELLED: - if (gDebugLogging) LOG(LogDebug) << "Scheduler " << id << " task was cancelled"; + if (gDebugLogging) LLOG(LogDebug) << "Scheduler " << id << " task was cancelled"; runnable.reset(); break; case l::concurrency::RunnableResult::SUCCESS: - if (gDebugLogging) LOG(LogDebug) << "Scheduler " << id << " task succeeded"; + if (gDebugLogging) LLOG(LogDebug) << "Scheduler " << id << " task succeeded"; mNumCompletedJobs++; runnable.reset(); break; case l::concurrency::RunnableResult::REQUEUE_DELAYED: runnable->Reschedule(); - if (gDebugLogging) LOG(LogDebug) << "Job '" + runnable->Name() + "' could not run yet and was requeued "; + if (gDebugLogging) LLOG(LogDebug) << "Job '" + runnable->Name() + "' could not run yet and was requeued "; queueJob(std::move(runnable)); break; case l::concurrency::RunnableResult::REQUEUE_BACKOFF: runnable->Backoff(); if (!runnable->Failed()) { - if (gDebugLogging) LOG(LogDebug) << "Job '" + runnable->Name() + "' was delayed and then requeued with backoff"; + if (gDebugLogging) LLOG(LogDebug) << "Job '" + runnable->Name() + "' was delayed and then requeued with backoff"; queueJob(std::move(runnable)); } else { - if (gDebugLogging) LOG(LogDebug) << "Job '" + runnable->Name() + "' failed and was cancelled"; + if (gDebugLogging) LLOG(LogDebug) << "Job '" + runnable->Name() + "' failed and was cancelled"; runnable.reset(); } break; case l::concurrency::RunnableResult::REQUEUE_IMMEDIATE: - if (gDebugLogging) LOG(LogInfo) << "Scheduler " << mName << " task was requeued"; + if (gDebugLogging) LLOG(LogInfo) << "Scheduler " << mName << " task was requeued"; queueJob(std::move(runnable)); break; } @@ -257,7 +257,7 @@ namespace l::concurrency { } mRunState.mNumRunningJobThreads--; - if (gDebugLogging) LOG(LogInfo) << "Scheduler " << mName << " exited"; + if (gDebugLogging) LLOG(LogInfo) << "Scheduler " << mName << " exited"; } } diff --git a/packages/concurrency/tests/common/ContainersTest.cpp b/packages/concurrency/tests/common/ContainersTest.cpp index 36bce7ce..ffd3aa87 100644 --- a/packages/concurrency/tests/common/ContainersTest.cpp +++ b/packages/concurrency/tests/common/ContainersTest.cpp @@ -100,9 +100,9 @@ TEST(Containers, Polymorphic) { TEST_TRUE(meta::Type::hash_code() != meta::Type::hash_code(), ""); TEST_TRUE(meta::Type::hash_code() != meta::Type::hash_code(), ""); - LOG(LogInfo) << meta::Type::hash_code(); - LOG(LogInfo) << meta::Type::hash_code(); - LOG(LogInfo) << meta::Type::hash_code(); + LLOG(LogInfo) << meta::Type::hash_code(); + LLOG(LogInfo) << meta::Type::hash_code(); + LLOG(LogInfo) << meta::Type::hash_code(); } { diff --git a/packages/concurrency/tests/common/ThreadingTest.cpp b/packages/concurrency/tests/common/ThreadingTest.cpp index a02ca30f..f2aea34c 100644 --- a/packages/concurrency/tests/common/ThreadingTest.cpp +++ b/packages/concurrency/tests/common/ThreadingTest.cpp @@ -27,20 +27,20 @@ TEST(Threading, ExecutorServiceStressTest) { int innerLoops = 100000; - LOG(LogInfo) << "Running " << numJobs << " jobs each doing " << innerLoops << "x some simple work."; + LLOG(LogInfo) << "Running " << numJobs << " jobs each doing " << innerLoops << "x some simple work."; for (int i = 0; i < numJobs; i++) { bool result = executor.queueJob(std::make_unique( "Worker " + std::to_string(i), [index = i, loops = innerLoops, &completedCount, &abortedCount](const l::concurrency::RunState& state) { - //LOG(LogDebug) << "Updating thread " << index; + //LLOG(LogDebug) << "Updating thread " << index; for (int j = 0; j < loops; j++) { j--; j++; j++; if (state.IsShuttingDown()) { abortedCount++; - //LOG(LogDebug) << "Breaking thread looping for shutdown on thread " << index; + //LLOG(LogDebug) << "Breaking thread looping for shutdown on thread " << index; return l::concurrency::RunnableResult::FAILURE; } } @@ -53,7 +53,7 @@ TEST(Threading, ExecutorServiceStressTest) { executor.startJobs(); - LOG(LogInfo) << "Ran " << completedCount << " jobs"; + LLOG(LogInfo) << "Ran " << completedCount << " jobs"; executor.pauseJobs(); @@ -64,13 +64,13 @@ TEST(Threading, ExecutorServiceStressTest) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); } - LOG(LogInfo) << "Ran " << completedCount << " and aborted " << abortedCount << " jobs"; + LLOG(LogInfo) << "Ran " << completedCount << " and aborted " << abortedCount << " jobs"; auto totalCount = completedCount + abortedCount; TEST_EQ(totalCount, 1000, "Count was wrong"); - LOG(LogInfo) << "Ran a total of " << totalCount << " jobs"; + LLOG(LogInfo) << "Ran a total of " << totalCount << " jobs"; return 0; } @@ -86,20 +86,20 @@ TEST(Threading, ExecutorServiceShutdown) { int innerLoops = 10000; - LOG(LogInfo) << "Running " << numJobs << " jobs each doing " << innerLoops << "x some simple work."; + LLOG(LogInfo) << "Running " << numJobs << " jobs each doing " << innerLoops << "x some simple work."; for (int i = 0; i < numJobs; i++) { bool result = executor.queueJob(std::make_unique( "Worker " + std::to_string(i), [index = i, loops = innerLoops, &completedCount, &abortedCount](const l::concurrency::RunState& state) { - //LOG(LogDebug) << "Updating thread " << index; + //LLOG(LogDebug) << "Updating thread " << index; for (int j = 0; j < loops; j++) { j--; j++; j++; if (state.IsShuttingDown()) { abortedCount++; - //LOG(LogDebug) << "Breaking thread looping for shutdown on thread " << index; + //LLOG(LogDebug) << "Breaking thread looping for shutdown on thread " << index; return l::concurrency::RunnableResult::FAILURE; } } @@ -114,14 +114,14 @@ TEST(Threading, ExecutorServiceShutdown) { std::this_thread::sleep_for(std::chrono::milliseconds(150)); } - LOG(LogInfo) << "Ran " << completedCount << " and aborted " << abortedCount << " jobs"; + LLOG(LogInfo) << "Ran " << completedCount << " and aborted " << abortedCount << " jobs"; auto totalCount = completedCount + abortedCount; TEST_EQ(totalCount, 5000, "Count was wrong"); - LOG(LogInfo) << "Ran a total of " << totalCount << " jobs"; + LLOG(LogInfo) << "Ran a total of " << totalCount << " jobs"; return 0; } diff --git a/packages/crypto/source/common/Crypto.cpp b/packages/crypto/source/common/Crypto.cpp index b26b453b..ac676b2b 100644 --- a/packages/crypto/source/common/Crypto.cpp +++ b/packages/crypto/source/common/Crypto.cpp @@ -316,7 +316,7 @@ namespace l::crypto { } else if (pubKey.size() == 44) { // rsa/pem encoded, first 12 bytes are id bytes - LOG(LogInfo) << "Loaded public key of type '" << std::string_view(pubKey.c_str(), 12) << "'"; + LLOG(LogInfo) << "Loaded public key of type '" << std::string_view(pubKey.c_str(), 12) << "'"; memcpy(mPubKey, pubKey.c_str() + 12, pubKey.size() - 12); } } @@ -341,7 +341,7 @@ namespace l::crypto { } else if (pubKey.size() == 44) { // rsa/pem encoded, first 12 bytes are id bytes - LOG(LogInfo) << "Loaded public key of type '" << std::string_view(pubKey.c_str(), 12) << "'"; + LLOG(LogInfo) << "Loaded public key of type '" << std::string_view(pubKey.c_str(), 12) << "'"; memcpy(mPubKey, pubKey.c_str() + 12, pubKey.size() - 12); } } diff --git a/packages/crypto/tests/common/CryptoppTest.cpp b/packages/crypto/tests/common/CryptoppTest.cpp index e2c393bb..f31fe423 100644 --- a/packages/crypto/tests/common/CryptoppTest.cpp +++ b/packages/crypto/tests/common/CryptoppTest.cpp @@ -53,7 +53,7 @@ TEST(CryptoPP, verifydigest) { mHmac.Update(p, message.size()); mHmac.Final(mSignature); auto sign = l::serialization::base16_encode(mSignature, 32); - LOG(LogTest) << sign; + LLOG(LogTest) << sign; /* echo -n 'apiKey=test' | openssl dgst -hex -sha256 -hmac 'NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j' @@ -81,8 +81,8 @@ TEST(Cryptopp, x25519test) { auto skxpemCorrectHex = l::serialization::base16_encode(l::serialization::base64_decode(skxpemCorrect)); auto pkxpemCorrectHex = l::serialization::base16_encode(l::serialization::base64_decode(pkxpemCorrect)); - LOG(LogInfo) << "Secret pem key in hex: " << skxpemCorrectHex; - LOG(LogInfo) << "Public pem key in hex: " << pkxpemCorrectHex; + LLOG(LogInfo) << "Secret pem key in hex: " << skxpemCorrectHex; + LLOG(LogInfo) << "Public pem key in hex: " << pkxpemCorrectHex; CryptoPP::byte privateKeyCorrect[32]; CryptoPP::byte publicKeyCorrect[32]; @@ -101,13 +101,13 @@ TEST(Cryptopp, x25519test) { auto pkxString = std::string_view(reinterpret_cast(publicKeyGen), 32); auto pkxPem = crypto::To25519PemKey(pkxString, true, true); - LOG(LogInfo) << "Public key pem format:\n" << pkxPem; + LLOG(LogInfo) << "Public key pem format:\n" << pkxPem; TEST_TRUE(pkxPem == pkxpemCorrect, ""); auto skxHex = l::serialization::base16_encode(privateKeyCorrect, 32); auto pkxHex = l::serialization::base16_encode(publicKeyGen, 32); - LOG(LogInfo) << "Public correct key hex format:\n" << pkxHex; + LLOG(LogInfo) << "Public correct key hex format:\n" << pkxHex; return 0; } @@ -145,8 +145,8 @@ bool TestGeneratedKey() { auto pkStr = std::string_view(reinterpret_cast(computedPublicKeyData), 32); auto skB16 = l::serialization::base16_encode(skStr); auto pkB16 = l::serialization::base16_encode(pkStr); - LOG(LogInfo) << "generated private:" << skB16; - LOG(LogInfo) << "generated public:" << pkB16; + LLOG(LogInfo) << "generated private:" << skB16; + LLOG(LogInfo) << "generated public:" << pkB16; TEST_FALSE(TestPublicKey(skB16, pkB16), ""); return 0; @@ -198,7 +198,7 @@ bool TestVerifier(std::string_view publicKeyB16, std::string_view message, std:: TEST(Cryptopp, printPemKeys) { auto messageHex = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"; auto signatureHex = "dc2a4459e7369633a52b1bf277839a00201009a3efbf3ecb69bea2186c26b58909351fc9ac90b3ecfdfbc7c66431e0303dca179c138ac17ad9bef1177331a704"; - LOG(LogInfo) << "private key pem: " << crypto::ToPemKey("833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42"); + LLOG(LogInfo) << "private key pem: " << crypto::ToPemKey("833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42"); return 0; } @@ -211,8 +211,8 @@ TEST(Cryptopp, xed) { CryptoPP::byte privateKey[32]; CryptoPP::byte publicKey[32]; xed.GenerateKeyPair(rand, privateKey, publicKey); - LOG(LogTest) << "private key: " << l::string::to_hex2(privateKey, 32); - LOG(LogTest) << "public key: " << l::string::to_hex2(publicKey, 32); + LLOG(LogTest) << "private key: " << l::string::to_hex2(privateKey, 32); + LLOG(LogTest) << "public key: " << l::string::to_hex2(publicKey, 32); } return 0; @@ -234,10 +234,10 @@ TEST(Cryptopp, printgenerated) { auto message = std::string_view("TestMessage"); auto len = signer.SignMessage(CryptoPP::NullRNG(), reinterpret_cast(message.data()), message.size(), sign); - LOG(LogTest) << "private key: " << l::serialization::base16_encode(sk, 32); - LOG(LogTest) << "public key: " << l::serialization::base16_encode(pk, 32); - LOG(LogTest) << "message: " << message; - LOG(LogTest) << "signature: " << l::serialization::base16_encode(sign, 64); + LLOG(LogTest) << "private key: " << l::serialization::base16_encode(sk, 32); + LLOG(LogTest) << "public key: " << l::serialization::base16_encode(pk, 32); + LLOG(LogTest) << "message: " << message; + LLOG(LogTest) << "signature: " << l::serialization::base16_encode(sign, 64); return 0; } @@ -251,8 +251,8 @@ TEST(Cryptopp, test2) { auto publicKeyB16 = "C5F9F54D52D5A2FB6AE692B1CCE695017C2EAD755B213D46AC6912F7B979C6CD"; auto messageB16 = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"; auto signatureB16 = "e30fc92c7548d99838f520eda491e5311ed9ce9fa868e5743191abe8d7f1a45e470f10cc9d23ddc3f5c906851c8b3d974c03006b9afc5bd6263d0fd72dcf5b09"; - LOG(LogInfo) << "private:" << privateKeyB16; - LOG(LogInfo) << "public:" << publicKeyB16; + LLOG(LogInfo) << "private:" << privateKeyB16; + LLOG(LogInfo) << "public:" << publicKeyB16; TEST_TRUE(!TestPublicKey(privateKeyB16, publicKeyB16), ""); TEST_TRUE(!TestSignature(privateKeyB16, messageB16, signatureB16), ""); TEST_TRUE(!TestVerifier(publicKeyB16, messageB16, signatureB16), ""); @@ -265,8 +265,8 @@ TEST(Cryptopp, test3) { auto publicKeyB16 = "EE8D0405408B1036B046F63923421C87AD9046CFB7FB23ED66A7DB0F6F7EDE90"; auto message = "TestMessage"; auto signatureB16 = "743D4194555C5F578F20D859A98DB1F93EB10297609EF3E2A459EE05513CA0D3DBEA5BFDECF17A3A3C9272C24A543882FBF6B717A4E35920CF71C64908C44D0F"; - LOG(LogInfo) << "private:" << privateKeyB16; - LOG(LogInfo) << "public:" << publicKeyB16; + LLOG(LogInfo) << "private:" << privateKeyB16; + LLOG(LogInfo) << "public:" << publicKeyB16; TEST_TRUE(!TestPublicKey(privateKeyB16, publicKeyB16), ""); TEST_TRUE(!TestSignature(privateKeyB16, message, signatureB16), ""); TEST_TRUE(!TestVerifier(publicKeyB16, message, signatureB16), ""); @@ -280,9 +280,9 @@ TEST(Cryptopp, PrintPKCS8) { crypto::CryptoXED25519 crypto; crypto.LoadPublicKeyHex(pk); - LOG(LogTest) << "pk: \n" << pk; - LOG(LogTest) << "Pem public key: \n" << crypto.GetPublicKeyPem(true); - LOG(LogTest) << "PKCS8 public key: \n" << crypto.GetPublicKeyPKCS8(); + LLOG(LogTest) << "pk: \n" << pk; + LLOG(LogTest) << "Pem public key: \n" << crypto.GetPublicKeyPem(true); + LLOG(LogTest) << "PKCS8 public key: \n" << crypto.GetPublicKeyPKCS8(); return 0; } @@ -313,16 +313,16 @@ TEST(Cryptopp, CryptoXED25519) { crypto.LoadPrivateKeyHex(privateKeyHex); TEST_TRUE(crypto.GetPrivateKeyHex() == privateKeyHex, ""); TEST_TRUE(crypto.GetPublicKeyHex() == publicKeyHex, ""); - LOG(LogInfo) << "Private key : " << crypto.GetPrivateKeyHex(); - LOG(LogTest) << "DER0 public key hex: " << crypto.SaveDERPublicKeyHex(false); - LOG(LogTest) << "DER1 public key hex: " << crypto.SaveDERPublicKeyHex(); - LOG(LogTest) << "DER0 public key b64: " << crypto.SaveDERPublicKeyB64(false); - LOG(LogTest) << "DER1 public key b64: \n" << crypto::ToPublicKeyFormat(crypto.SaveDERPublicKeyB64(1)); - LOG(LogTest) << "PEM public key: \n" << crypto.GetPublicKeyPem(true); - LOG(LogTest) << "PKCS8 public key: \n" << crypto.GetPublicKeyPKCS8(); + LLOG(LogInfo) << "Private key : " << crypto.GetPrivateKeyHex(); + LLOG(LogTest) << "DER0 public key hex: " << crypto.SaveDERPublicKeyHex(false); + LLOG(LogTest) << "DER1 public key hex: " << crypto.SaveDERPublicKeyHex(); + LLOG(LogTest) << "DER0 public key b64: " << crypto.SaveDERPublicKeyB64(false); + LLOG(LogTest) << "DER1 public key b64: \n" << crypto::ToPublicKeyFormat(crypto.SaveDERPublicKeyB64(1)); + LLOG(LogTest) << "PEM public key: \n" << crypto.GetPublicKeyPem(true); + LLOG(LogTest) << "PKCS8 public key: \n" << crypto.GetPublicKeyPKCS8(); crypto.AccumulateMessage(message); auto signature = crypto.SignMessageB64(); - LOG(LogTest) << "Sign: \n" << signature; + LLOG(LogTest) << "Sign: \n" << signature; { crypto::CryptoXED25519 cryptoV; diff --git a/packages/crypto/tests/common/ed25519Test.cpp b/packages/crypto/tests/common/ed25519Test.cpp index 869ff326..bb5fce10 100644 --- a/packages/crypto/tests/common/ed25519Test.cpp +++ b/packages/crypto/tests/common/ed25519Test.cpp @@ -21,8 +21,8 @@ TEST(Cryptopp, ed25519test) { auto skpemCorrectHex = l::serialization::base16_encode(l::serialization::base64_decode(skpemCorrect)); auto pkpemCorrectHex = l::serialization::base16_encode(l::serialization::base64_decode(pkpemCorrect)); - LOG(LogInfo) << "Secret pem key in hex: " << skpemCorrectHex; - LOG(LogInfo) << "Public pem key in hex: " << pkpemCorrectHex; + LLOG(LogInfo) << "Secret pem key in hex: " << skpemCorrectHex; + LLOG(LogInfo) << "Public pem key in hex: " << pkpemCorrectHex; unsigned char seed[32]; unsigned char sk[64]; @@ -39,9 +39,9 @@ TEST(Cryptopp, ed25519test) { auto skHex = l::serialization::base16_encode(skStr); auto pkHex = l::serialization::base16_encode(pkStr); - LOG(LogInfo) << "seed hex: " << seedHex; - LOG(LogInfo) << "secret key hex: " << skHex; - LOG(LogInfo) << "public key hex: " << pkHex; + LLOG(LogInfo) << "seed hex: " << seedHex; + LLOG(LogInfo) << "secret key hex: " << skHex; + LLOG(LogInfo) << "public key hex: " << pkHex; auto message = std::string_view("testmessage"); @@ -49,7 +49,7 @@ TEST(Cryptopp, ed25519test) { auto sgStr = std::string_view(reinterpret_cast(sg), 64); auto sgHex = l::serialization::base16_encode(sgStr); - LOG(LogInfo) << "signature of 'testmessage': " << sgHex; + LLOG(LogInfo) << "signature of 'testmessage': " << sgHex; return 0; } @@ -65,36 +65,36 @@ TEST(Crypto, ed2519) { crypto::CryptoED25519 ed25519; ed25519.CreateKeys(pubKey, priKey); - LOG(LogTest) << "public key base64: " << ed25519.GetPubKeyBase64(); - LOG(LogTest) << "private key base64: " << ed25519.GetPriKeyBase64(); - LOG(LogTest) << "public key hex: " << ed25519.GetPubKeyHex(); - LOG(LogTest) << "private key hex: " << ed25519.GetPriKeyHex(); - LOG(LogTest) << "public key pem2: " << ed25519.GetPubKeyPem(); + LLOG(LogTest) << "public key base64: " << ed25519.GetPubKeyBase64(); + LLOG(LogTest) << "private key base64: " << ed25519.GetPriKeyBase64(); + LLOG(LogTest) << "public key hex: " << ed25519.GetPubKeyHex(); + LLOG(LogTest) << "private key hex: " << ed25519.GetPriKeyHex(); + LLOG(LogTest) << "public key pem2: " << ed25519.GetPubKeyPem(); auto signature = ed25519.GetSign(message); - LOG(LogTest) << "signature:" << signature; + LLOG(LogTest) << "signature:" << signature; TEST_FALSE(ed25519.Verify(signature, message + "s", pubKey), ""); TEST_TRUE(ed25519.Verify(signature, message), pubKey); // Signature // r5YGa0VjkFxOgoY1dMyfH6Jf3j0fIzx5oY/V10Xc4b4Mu6tXrF7RZgQXWLCAfonrJdtDKL99wNuB0RGaJSVyAw== - LOG(LogTest) << "signature key hex: " << l::string::to_hex2(l::serialization::base64_decode(signature)); + LLOG(LogTest) << "signature key hex: " << l::string::to_hex2(l::serialization::base64_decode(signature)); // Examples private keys // MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC - LOG(LogTest) << "private key hex: " << l::string::to_hex2(l::serialization::base64_decode(ed25519.GetPriKeyBase64())); - LOG(LogTest) << l::string::to_hex2(l::serialization::base64_decode("MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC")); + LLOG(LogTest) << "private key hex: " << l::string::to_hex2(l::serialization::base64_decode(ed25519.GetPriKeyBase64())); + LLOG(LogTest) << l::string::to_hex2(l::serialization::base64_decode("MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC")); // Examples public key with prefix separately printed - LOG(LogTest) << l::string::to_hex2(l::serialization::base64_decode("MCowBQYDK2VwAyEA")); + LLOG(LogTest) << l::string::to_hex2(l::serialization::base64_decode("MCowBQYDK2VwAyEA")); - LOG(LogTest) << l::string::to_hex2(l::serialization::base64_decode("MCowBQYDK2VwAyEAVBGfgCzo9ILV1gGq0UuqIRwcbL1RMCSxYPGpdjHxaOk=")); + LLOG(LogTest) << l::string::to_hex2(l::serialization::base64_decode("MCowBQYDK2VwAyEAVBGfgCzo9ILV1gGq0UuqIRwcbL1RMCSxYPGpdjHxaOk=")); - //LOG(LogTest) << l::string::to_hex2(l::serialization::base64_decode("MCowBQYDK2VwAyEAhh0h5M77+TuNChqNfxFiOqAT5fy6UbHsO6M4pDGmEuE=")); - //LOG(LogTest) << l::string::to_hex2(l::serialization::base64_decode("MCowBQYDK2VwAyEAgmDRTtj2FA+wzJUIlAL9ly1eovjLBu7uXUFR+jFULmg=")); - //LOG(LogTest) << l::string::to_hex2(l::serialization::base64_decode("MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=")); + //LLOG(LogTest) << l::string::to_hex2(l::serialization::base64_decode("MCowBQYDK2VwAyEAhh0h5M77+TuNChqNfxFiOqAT5fy6UbHsO6M4pDGmEuE=")); + //LLOG(LogTest) << l::string::to_hex2(l::serialization::base64_decode("MCowBQYDK2VwAyEAgmDRTtj2FA+wzJUIlAL9ly1eovjLBu7uXUFR+jFULmg=")); + //LLOG(LogTest) << l::string::to_hex2(l::serialization::base64_decode("MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=")); // MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= return 0; @@ -105,10 +105,10 @@ TEST(Crypto, generate) { ed25519.CreateKeys(); auto message = "TestMessage"; auto sign = ed25519.GetSign(message); - LOG(LogTest) << "public key: " << ed25519.GetPubKeyHex(); - LOG(LogTest) << "private key: " << ed25519.GetPriKeyHex(); - LOG(LogTest) << "message: " << message; - LOG(LogTest) << "signature: " << l::serialization::base16_encode(sign); + LLOG(LogTest) << "public key: " << ed25519.GetPubKeyHex(); + LLOG(LogTest) << "private key: " << ed25519.GetPriKeyHex(); + LLOG(LogTest) << "message: " << message; + LLOG(LogTest) << "signature: " << l::serialization::base16_encode(sign); return 0; } @@ -119,10 +119,10 @@ TEST(Crypto, validation) { auto publicKey = l::string::hex_decode("ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf"); auto message = l::string::hex_decode("ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"); auto signature = l::string::hex_decode("dc2a4459e7369633a52b1bf277839a00201009a3efbf3ecb69bea2186c26b58909351fc9ac90b3ecfdfbc7c66431e0303dca179c138ac17ad9bef1177331a704"); - LOG(LogTest) << "secret hex:" << l::string::to_hex2(secret); - LOG(LogTest) << "publicKey hex:" << l::string::to_hex2(publicKey); - LOG(LogTest) << "message hex:" << l::string::to_hex2(message); - LOG(LogTest) << "signature hex:" << l::string::to_hex2(signature); + LLOG(LogTest) << "secret hex:" << l::string::to_hex2(secret); + LLOG(LogTest) << "publicKey hex:" << l::string::to_hex2(publicKey); + LLOG(LogTest) << "message hex:" << l::string::to_hex2(message); + LLOG(LogTest) << "signature hex:" << l::string::to_hex2(signature); auto secretBase64 = l::serialization::base64_encode(secret); auto publicKeyBase64 = l::serialization::base64_encode(publicKey); diff --git a/packages/ecs/include/ecs/entityecs/BaseSystem.h b/packages/ecs/include/ecs/entityecs/BaseSystem.h index bf3b7858..7f9f132b 100644 --- a/packages/ecs/include/ecs/entityecs/BaseSystem.h +++ b/packages/ecs/include/ecs/entityecs/BaseSystem.h @@ -36,11 +36,11 @@ class BaseSystem : public EntitySystem2, } virtual void receive(class World*, const Events::OnEntityCreated& event) { - LOG(LogTest) << "An entity was created! " << event.entity->getEntityId(); + LLOG(LogTest) << "An entity was created! " << event.entity->getEntityId(); } virtual void receive(class World*, const Events::OnEntityDestroyed& event) { - LOG(LogTest) << "An entity was destroyed! " << event.entity->getEntityId(); + LLOG(LogTest) << "An entity was destroyed! " << event.entity->getEntityId(); } auto& getComponents() { diff --git a/packages/ecs/include/ecs/entityecs/ECSExt.h b/packages/ecs/include/ecs/entityecs/ECSExt.h index 362906cb..16298cc7 100644 --- a/packages/ecs/include/ecs/entityecs/ECSExt.h +++ b/packages/ecs/include/ecs/entityecs/ECSExt.h @@ -353,7 +353,7 @@ namespace l::ecs { } } if (!it->second) { - LOG(LogError) << "ComponeneViewCache has map entry, but is missing the actual cache for types: " << types_to_string(); + LLOG(LogError) << "ComponeneViewCache has map entry, but is missing the actual cache for types: " << types_to_string(); mComponentCacheMap.erase(it); return nullptr; } diff --git a/packages/ecs/tests/common/EntityECS2Test.cpp b/packages/ecs/tests/common/EntityECS2Test.cpp index ace7b919..49fb088d 100644 --- a/packages/ecs/tests/common/EntityECS2Test.cpp +++ b/packages/ecs/tests/common/EntityECS2Test.cpp @@ -61,7 +61,7 @@ class TestSystem2 : public EntitySystem2, virtual void receive(class World* world, const DeleteEvent& event) override { - LOG(LogTest) << "I received SomeEvent with value " << event.num << "!"; + LLOG(LogTest) << "I received SomeEvent with value " << event.num << "!"; // Let's delete an entity while iterating because why not? world->all([&](Entity* ent) { @@ -69,7 +69,7 @@ class TestSystem2 : public EntitySystem2, world->destroy(world->getById(event.num)); if (ent->getEntityId() == event.num) - LOG(LogInfo) << "Woah, we shouldn't get here!"; + LLOG(LogInfo) << "Woah, we shouldn't get here!"; }); } @@ -78,7 +78,7 @@ class TestSystem2 : public EntitySystem2, }; TEST(EntityECS2, Sample) { - LOG(LogInfo) << "EntityComponentSystem Test"; + LLOG(LogInfo) << "EntityComponentSystem Test"; auto world = World2::createWorld(); @@ -98,17 +98,17 @@ TEST(EntityECS2, Sample) { world->tick({ 0.0f, 10.f }); - LOG(LogInfo) << "After tick(10) and EnableSystem(testSystem): position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")"; + LLOG(LogInfo) << "After tick(10) and EnableSystem(testSystem): position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")"; int count = 0; - LOG(LogInfo) << "Counting entities with SomeComponent..."; + LLOG(LogInfo) << "Counting entities with SomeComponent..."; // range based for loop world->each2([&](Entity* ent, ComponentHandle p, ComponentHandle r) { ++count; - //LOG(LogInfo) << "Found entity #" << ent->getEntityId() << ", p{" << p->x << "," << p->y << "}" << ", r{" << r->angle << "}"; + //LLOG(LogInfo) << "Found entity #" << ent->getEntityId() << ", p{" << p->x << "," << p->y << "}" << ", r{" << r->angle << "}"; }); - LOG(LogInfo) << count << " entities have position and rotation!"; + LLOG(LogInfo) << count << " entities have position and rotation!"; for (int i = 0; i < 10; ++i) { @@ -117,7 +117,7 @@ TEST(EntityECS2, Sample) { } // Emitting events - LOG(LogInfo) << "Emit 'SomeEvent' to entity id 4"; + LLOG(LogInfo) << "Emit 'SomeEvent' to entity id 4"; world->emit({ 4 }); world->cleanup(); @@ -149,7 +149,7 @@ TEST(EntityECS2, BugMultipleEntitiesInViewCache) { world->tick({ 0.0f, 10.f }); world->each2([&](Entity* ent, ComponentHandle p, ComponentHandle r) { - LOG(LogInfo) << "Found entity #" << ent->getEntityId() << ", p{" << p->x << "," << p->y << "}" << ", r{" << r->angle << "}"; + LLOG(LogInfo) << "Found entity #" << ent->getEntityId() << ", p{" << p->x << "," << p->y << "}" << ", r{" << r->angle << "}"; }); auto cache = world->getComponentCache(); @@ -239,10 +239,10 @@ PERF_TEST(EntityECS2, EntityStressTest) }); } - LOG(LogInfo) << count << " entities have position and rotation!"; + LLOG(LogInfo) << count << " entities have position and rotation!"; // Emitting events - LOG(LogInfo) << "Emit 'SomeEvent' to entity id 4"; + LLOG(LogInfo) << "Emit 'SomeEvent' to entity id 4"; world->emit({ 5000 }); world->cleanup(); diff --git a/packages/ecs/tests/common/EntityECSTest.cpp b/packages/ecs/tests/common/EntityECSTest.cpp index f35dcbea..b57523e8 100644 --- a/packages/ecs/tests/common/EntityECSTest.cpp +++ b/packages/ecs/tests/common/EntityECSTest.cpp @@ -98,17 +98,17 @@ class TestSystem : public EntitySystem2, virtual void receive(class World* world, const Events::OnComponentRemoved& event) override { - LOG(LogTest) << "A position component was removed!"; + LLOG(LogTest) << "A position component was removed!"; } virtual void receive(class World* world, const Events::OnComponentRemoved& event) override { - LOG(LogTest) << "A rotation component was removed! "; + LLOG(LogTest) << "A rotation component was removed! "; } virtual void receive(class World* world, const DeleteEvent& event) override { - LOG(LogTest) << "I received SomeEvent with value " << event.num << "!"; + LLOG(LogTest) << "I received SomeEvent with value " << event.num << "!"; // Let's delete an entity while iterating because why not? world->all([&](Entity* ent) { @@ -116,7 +116,7 @@ class TestSystem : public EntitySystem2, world->destroy(world->getById(event.num)); if (ent->getEntityId() == event.num) - LOG(LogInfo) << "Woah, we shouldn't get here!"; + LLOG(LogInfo) << "Woah, we shouldn't get here!"; }); } @@ -138,7 +138,7 @@ TEST(EntityECS, ComponentCache) TEST(EntityECS, LifeCycle) { - LOG(LogInfo) << "EntityComponentSystem Test"; + LLOG(LogInfo) << "EntityComponentSystem Test"; auto world = World2::createWorld(); @@ -159,7 +159,7 @@ TEST(EntityECS, LifeCycle) TEST(EntityECS, Sample) { - LOG(LogInfo) << "EntityComponentSystem Test"; + LLOG(LogInfo) << "EntityComponentSystem Test"; auto world = World2::createWorld(); @@ -169,21 +169,21 @@ TEST(EntityECS, Sample) auto pos = ent->assign(0.f, 0.f); auto rot = ent->assign(0.f); - LOG(LogInfo) << "size of an entity: " << sizeof(Entity); - LOG(LogInfo) << "size of Position: " << sizeof(Position); - LOG(LogInfo) << "size of Rotation: " << sizeof(Rotation); + LLOG(LogInfo) << "size of an entity: " << sizeof(Entity); + LLOG(LogInfo) << "size of Position: " << sizeof(Position); + LLOG(LogInfo) << "size of Rotation: " << sizeof(Rotation); - LOG(LogInfo) << "Initial values: position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")"; + LLOG(LogInfo) << "Initial values: position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")"; world->tick({ 0.0f, 10.f }); - LOG(LogInfo) << "After tick(10): position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")"; + LLOG(LogInfo) << "After tick(10): position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")"; world->disableSystem(entitySystem); world->tick({ 0.0f, 10.f }); - LOG(LogInfo) << "After tick(10) and DisableSystem(testSystem): position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")"; + LLOG(LogInfo) << "After tick(10) and DisableSystem(testSystem): position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")"; world->enableSystem(entitySystem); @@ -193,12 +193,12 @@ TEST(EntityECS, Sample) TEST_FUZZY2(pos->y, 20.0f, ""); TEST_FUZZY2(rot->angle, 40.0f, ""); - LOG(LogInfo) << "After tick(10) and EnableSystem(testSystem): position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")"; + LLOG(LogInfo) << "After tick(10) and EnableSystem(testSystem): position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")"; ent->remove(); ent->remove(); - LOG(LogInfo) << "Creating more entities..."; + LLOG(LogInfo) << "Creating more entities..."; for (int i = 0; i < 10; ++i) { @@ -207,28 +207,28 @@ TEST(EntityECS, Sample) } int count = 0; - LOG(LogInfo) << "Counting entities with SomeComponent..."; + LLOG(LogInfo) << "Counting entities with SomeComponent..."; // range based for loop for (auto ent : world->each()) { ++count; - LOG(LogInfo) << "Found entity #" << ent->getEntityId(); + LLOG(LogInfo) << "Found entity #" << ent->getEntityId(); } - LOG(LogInfo) << count << " entities have SomeComponent!"; + LLOG(LogInfo) << count << " entities have SomeComponent!"; // Emitting events world->emit({ 4 }); TEST_EQ(world->getCount(), 11, ""); - LOG(LogInfo) << "We have " << world->getCount() << " entities right now."; + LLOG(LogInfo) << "We have " << world->getCount() << " entities right now."; world->cleanup(); TEST_EQ(world->getCount(), 10, ""); - LOG(LogInfo) << "After a cleanup, we have " << world->getCount() << " entities."; + LLOG(LogInfo) << "After a cleanup, we have " << world->getCount() << " entities."; - LOG(LogInfo) << "Destroying the world..."; + LLOG(LogInfo) << "Destroying the world..."; world->destroyWorld(); diff --git a/packages/filesystem/source/common/filesystem/File.cpp b/packages/filesystem/source/common/filesystem/File.cpp index 57d51a7e..ebeeda6d 100644 --- a/packages/filesystem/source/common/filesystem/File.cpp +++ b/packages/filesystem/source/common/filesystem/File.cpp @@ -238,7 +238,7 @@ namespace filesystem { size_t File::read(char* dst, size_t count) { if (!mFileStream) { - LOG(LogWarning) << "File not open:" << mFilePath; + LLOG(LogWarning) << "File not open:" << mFilePath; ASSERT(mFileStream.has_value()) << "File not open:" << mFilePath; return 0; } @@ -250,7 +250,7 @@ namespace filesystem { size_t File::write(const char* src, size_t count) { if (!mFileStream) { - LOG(LogWarning) << "File not open:" << mFilePath; + LLOG(LogWarning) << "File not open:" << mFilePath; ASSERT(mFileStream.has_value()) << "File not open:" << mFilePath; return 0; } @@ -260,7 +260,7 @@ namespace filesystem { size_t File::read(std::vector& dst) { if (!mFileStream) { - LOG(LogWarning) << "File not open:" << mFilePath; + LLOG(LogWarning) << "File not open:" << mFilePath; ASSERT(mFileStream.has_value()) << "File not open:" << mFilePath; return 0; } @@ -274,7 +274,7 @@ namespace filesystem { size_t File::write(const std::vector& src) { if (!mFileStream) { - LOG(LogWarning) << "File not open:" << mFilePath; + LLOG(LogWarning) << "File not open:" << mFilePath; ASSERT(mFileStream.has_value()) << "File not open:" << mFilePath; return 0; } @@ -284,7 +284,7 @@ namespace filesystem { size_t File::read(unsigned char* dst, size_t count) { if (!mFileStream) { - LOG(LogWarning) << "File not open:" << mFilePath; + LLOG(LogWarning) << "File not open:" << mFilePath; ASSERT(mFileStream.has_value()) << "File not open:" << mFilePath; return 0; } @@ -296,7 +296,7 @@ namespace filesystem { size_t File::write(const unsigned char* src, size_t count) { if (!mFileStream) { - LOG(LogWarning) << "File not open:" << mFilePath; + LLOG(LogWarning) << "File not open:" << mFilePath; ASSERT(mFileStream.has_value()) << "File not open:" << mFilePath; return 0; } @@ -306,7 +306,7 @@ namespace filesystem { size_t File::read(std::vector& dst) { if (!mFileStream) { - LOG(LogWarning) << "File not open:" << mFilePath; + LLOG(LogWarning) << "File not open:" << mFilePath; ASSERT(mFileStream.has_value()) << "File not open:" << mFilePath; return 0; } @@ -320,7 +320,7 @@ namespace filesystem { size_t File::write(const std::vector& src) { if (!mFileStream) { - LOG(LogWarning) << "File not open:" << mFilePath; + LLOG(LogWarning) << "File not open:" << mFilePath; ASSERT(mFileStream.has_value()) << "File not open:" << mFilePath; return 0; } @@ -330,7 +330,7 @@ namespace filesystem { size_t File::read(std::stringstream& dst, size_t count) { if (!mFileStream) { - LOG(LogWarning) << "File not open:" << mFilePath; + LLOG(LogWarning) << "File not open:" << mFilePath; ASSERT(mFileStream.has_value()) << "File not open:" << mFilePath; return 0; } @@ -352,7 +352,7 @@ namespace filesystem { size_t File::write(std::stringstream& src, size_t count) { if (!mFileStream) { - LOG(LogWarning) << "File not open:" << mFilePath; + LLOG(LogWarning) << "File not open:" << mFilePath; ASSERT(mFileStream.has_value()) << "File not open:" << mFilePath; return 0; } diff --git a/packages/hid/include/hid/KeyState.h b/packages/hid/include/hid/KeyState.h index 4addfb88..4fdd967d 100644 --- a/packages/hid/include/hid/KeyState.h +++ b/packages/hid/include/hid/KeyState.h @@ -37,6 +37,9 @@ namespace l::hid { } ~KeyState() = default; + std::tuple LastKeyPressed(); + void UpdateKeyPress(int32_t scanCode); + void UpdateKeyDown(int32_t keyCode); void UpdateKeyUp(int32_t keyCode); bool IsReleased(int32_t keyCode); @@ -47,6 +50,8 @@ namespace l::hid { void ForEachKeyChange(std::function keyHandler); void ForEachKey(std::function keyHandler); protected: + int32_t mLastKeyPressed = 0; + int32_t mLastKeyDetections = 0; std::unordered_map mActiveKeys; }; } diff --git a/packages/hid/source/common/KeyState.cpp b/packages/hid/source/common/KeyState.cpp index d6f7d1bf..f1c1e2e6 100644 --- a/packages/hid/source/common/KeyState.cpp +++ b/packages/hid/source/common/KeyState.cpp @@ -30,7 +30,7 @@ namespace l::hid { releasedPrev = released; //if (pressedNow || releasedNow || pressed) { - // LOG(LogInfo) << "pressed now: " << pressedNow << ", released now: " << releasedNow << ", pressed: " << pressed; + // LLOG(LogInfo) << "pressed now: " << pressedNow << ", released now: " << releasedNow << ", pressed: " << pressed; //} } @@ -50,6 +50,20 @@ namespace l::hid { return pressedNow; } + std::tuple KeyState::LastKeyPressed() { + return { static_cast(mLastKeyPressed), mLastKeyDetections }; + } + + void KeyState::UpdateKeyPress(int32_t scanCode) { + if (scanCode >= 32 && scanCode <= 126) { + if (mLastKeyPressed != scanCode) { + mLastKeyDetections = 0; + } + mLastKeyPressed = scanCode; + mLastKeyDetections++; + } + } + void KeyState::UpdateKeyDown(int32_t keyCode) { mActiveKeys[keyCode].UpdateKeyDown(); } diff --git a/packages/hid/source/common/Midi.cpp b/packages/hid/source/common/Midi.cpp index 178f1994..683afa41 100644 --- a/packages/hid/source/common/Midi.cpp +++ b/packages/hid/source/common/Midi.cpp @@ -14,28 +14,28 @@ namespace l::hid::midi { void HandleMidiData(uint32_t msg, uint32_t deviceId, uint32_t deviceOutId, uint32_t param1, uint32_t param2) { switch (msg) { case MIM_OPEN: - //LOG(LogInfo) << "MIM_OPEN"; + //LLOG(LogInfo) << "MIM_OPEN"; return; case MIM_CLOSE: - //LOG(LogInfo) << "MIM_CLOSE"; + //LLOG(LogInfo) << "MIM_CLOSE"; return; case MIM_LONGDATA: - LOG(LogInfo) << "Long data: " << msg << " " << deviceId << " " << param1 << " " << param2; + LLOG(LogInfo) << "Long data: " << msg << " " << deviceId << " " << param1 << " " << param2; return; case MIM_ERROR: - LOG(LogInfo) << "Error: " << msg << " " << deviceId << " " << param1 << " " << param2; + LLOG(LogInfo) << "Error: " << msg << " " << deviceId << " " << param1 << " " << param2; return; case MIM_LONGERROR: - LOG(LogInfo) << "Long error: " << msg << " " << deviceId << " " << param1 << " " << param2; + LLOG(LogInfo) << "Long error: " << msg << " " << deviceId << " " << param1 << " " << param2; return; case MIM_MOREDATA: - LOG(LogInfo) << "More data: " << msg << " " << deviceId << " " << param1 << " " << param2; + LLOG(LogInfo) << "More data: " << msg << " " << deviceId << " " << param1 << " " << param2; return; case MIM_DATA: - //LOG(LogInfo) << "Data: " << msg << " " << deviceId << " " << param1 << " " << param2; + //LLOG(LogInfo) << "Data: " << msg << " " << deviceId << " " << param1 << " " << param2; break; default: - LOG(LogInfo) << "default"; + LLOG(LogInfo) << "default"; break; } diff --git a/packages/hid/source/windows/MidiWindows.cpp b/packages/hid/source/windows/MidiWindows.cpp index 7e3b18bb..112b0c35 100644 --- a/packages/hid/source/windows/MidiWindows.cpp +++ b/packages/hid/source/windows/MidiWindows.cpp @@ -22,16 +22,16 @@ namespace l::hid::midi { //auto id = midiPtr->getDeviceOutId(deviceOut); switch (wMsg) { case MOM_OPEN: - //LOG(LogInfo) << "MOM_OPEN"; + //LLOG(LogInfo) << "MOM_OPEN"; break; case MOM_CLOSE: - //LOG(LogInfo) << "MOM_CLOSE"; + //LLOG(LogInfo) << "MOM_CLOSE"; break; case MOM_DONE: - //LOG(LogInfo) << "MOM_DONE"; + //LLOG(LogInfo) << "MOM_DONE"; break; default: - //LOG(LogInfo) << "Something else.."; + //LLOG(LogInfo) << "Something else.."; break; } @@ -47,14 +47,14 @@ namespace l::hid::midi { MMRESULT rv; rv = midiOutClose(device.second); if (rv != MMSYSERR_NOERROR) { - LOG(LogError) << "Failed to close midi out device" << deviceId; + LLOG(LogError) << "Failed to close midi out device" << deviceId; } rv = midiInStop(device.first); if (rv == MMSYSERR_NOERROR) { rv = midiInClose(device.first); } if (rv != MMSYSERR_NOERROR) { - LOG(LogError) << "Failed to close midi in device" << deviceId; + LLOG(LogError) << "Failed to close midi in device" << deviceId; } deviceId++; } @@ -76,8 +76,8 @@ namespace l::hid::midi { UINT nMidiOutDeviceNum = midiOutGetNumDevs(); - LOG(LogInfo) << "Number of midi in devices: " << nMidiInDeviceInNum; - LOG(LogInfo) << "Number of midi out devices: " << nMidiOutDeviceNum; + LLOG(LogInfo) << "Number of midi in devices: " << nMidiInDeviceInNum; + LLOG(LogInfo) << "Number of midi out devices: " << nMidiOutDeviceNum; for (uint32_t deviceId = 0; deviceId < nMidiInDeviceInNum || deviceId < nMidiOutDeviceNum; deviceId++) { MMRESULT rv; @@ -89,22 +89,22 @@ namespace l::hid::midi { if (deviceId < nMidiInDeviceInNum) { rv = midiInGetDevCaps(deviceId, &capsIn, sizeof(MIDIINCAPS)); if (rv != MMSYSERR_NOERROR) { - LOG(LogError) << "Failed to get midi in caps on device " << deviceId; + LLOG(LogError) << "Failed to get midi in caps on device " << deviceId; } - LOG(LogInfo) << "Midi in device id " << deviceId << " : " << capsIn.szPname << ", support : " << capsIn.dwSupport << ", pid : " << capsIn.wPid; + LLOG(LogInfo) << "Midi in device id " << deviceId << " : " << capsIn.szPname << ", support : " << capsIn.dwSupport << ", pid : " << capsIn.wPid; rv = midiInOpen(&hMidiDeviceIn, static_cast(deviceId), reinterpret_cast(&details::MidiInProc), (DWORD_PTR)(this), CALLBACK_FUNCTION | MIDI_IO_STATUS); if (rv == MMSYSERR_ALLOCATED) { return; } if (rv != MMSYSERR_NOERROR) { - LOG(LogError) << "Failed to open midi in device " << deviceId; + LLOG(LogError) << "Failed to open midi in device " << deviceId; continue; } rv = midiInStart(hMidiDeviceIn); if (rv != MMSYSERR_NOERROR) { - LOG(LogError) << "Failed to start midi in device" << deviceId; + LLOG(LogError) << "Failed to start midi in device" << deviceId; continue; } } @@ -112,16 +112,16 @@ namespace l::hid::midi { if (deviceId < nMidiOutDeviceNum) { rv = midiOutGetDevCaps(deviceId, &capsOut, sizeof(MIDIOUTCAPS)); if (rv != MMSYSERR_NOERROR) { - LOG(LogError) << "Failed to get midi out caps on device " << deviceId; + LLOG(LogError) << "Failed to get midi out caps on device " << deviceId; } - LOG(LogInfo) << "Midi out device id " << deviceId << " : " << capsOut.szPname << ", support : " << capsOut.dwSupport << ", pid : " << capsOut.wPid; + LLOG(LogInfo) << "Midi out device id " << deviceId << " : " << capsOut.szPname << ", support : " << capsOut.dwSupport << ", pid : " << capsOut.wPid; rv = midiOutOpen(&hMidiDeviceOut, static_cast(deviceId), reinterpret_cast(&details::MidiOutProc), (DWORD_PTR)(this), CALLBACK_FUNCTION); if (rv == MMSYSERR_ALLOCATED) { return; } if (rv != MMSYSERR_NOERROR) { - LOG(LogError) << "Failed to open midi out device " << deviceId; + LLOG(LogError) << "Failed to open midi out device " << deviceId; } } @@ -247,17 +247,17 @@ namespace l::hid::midi { MMRESULT rv = midiOutPrepareHeader(deviceOut, &mHeader, sizeof(MIDIHDR)); if (rv != MMSYSERR_NOERROR) { - LOG(LogError) << "Failed to prepare midi out buffer " << deviceId << ", error " << rv; + LLOG(LogError) << "Failed to prepare midi out buffer " << deviceId << ", error " << rv; } rv = midiOutLongMsg(deviceOut, &mHeader, sizeof(MIDIHDR)); if (rv != MMSYSERR_NOERROR) { - LOG(LogError) << "Failed to send buffer to midi out device " << deviceId << ", error " << rv; + LLOG(LogError) << "Failed to send buffer to midi out device " << deviceId << ", error " << rv; } rv = midiOutUnprepareHeader(deviceOut, &mHeader, sizeof(MIDIHDR)); if (rv != MMSYSERR_NOERROR) { - LOG(LogError) << "Failed to unprepare midi out buffer " << deviceId << ", error " << rv; + LLOG(LogError) << "Failed to unprepare midi out buffer " << deviceId << ", error " << rv; } } @@ -271,7 +271,7 @@ namespace l::hid::midi { MMRESULT rv = midiOutShortMsg(device, param1); if (rv != MMSYSERR_NOERROR) { - LOG(LogError) << "Failed to send to midi out device " << deviceId << ", error " << rv << ", data " << param1; + LLOG(LogError) << "Failed to send to midi out device " << deviceId << ", error " << rv << ", data " << param1; } } @@ -303,7 +303,7 @@ namespace l::hid::midi { mMidiDevice.initDevices(); RegisterCallback([](const MidiData&) { - //LOG(LogInfo) << "midi cb: device in:" << data.deviceIn << " device out:" << data.deviceOut << " stat:" << data.status << " ch:" << data.channel << " d1:" << data.data1 << " d2:" << data.data2; + //LLOG(LogInfo) << "midi cb: device in:" << data.deviceIn << " device out:" << data.deviceOut << " stat:" << data.status << " ch:" << data.channel << " d1:" << data.data1 << " d2:" << data.data2; }); } diff --git a/packages/logging/include/logging/Log.h b/packages/logging/include/logging/Log.h index 580a9fea..9d2c7164 100644 --- a/packages/logging/include/logging/Log.h +++ b/packages/logging/include/logging/Log.h @@ -95,7 +95,7 @@ namespace logging { #define LOG_LEVEL_ON(level) l::logging::SetLogLevelOn(l::logging::LogLevel::level, true) #define LOG_LEVEL_OFF(level) l::logging::SetLogLevelOn(l::logging::LogLevel::level, false) -#define LOG(level) l::logging::LogMessage(__FILE__, __LINE__, l::logging::LogLevel::level) +#define LLOG(level) l::logging::LogMessage(__FILE__, __LINE__, l::logging::LogLevel::level) #define ASSERT(condition) l::logging::LogMessage(__FILE__, __LINE__, l::logging::LogLevel::LogAssertion, condition) @@ -103,6 +103,12 @@ namespace logging { #define EXPECT(condition) l::logging::LogMessage(__FILE__, __LINE__, l::logging::LogLevel::LogExpection, condition) +#define LASSERT(condition) l::logging::LogMessage(__FILE__, __LINE__, l::logging::LogLevel::LogAssertion, condition) + +#define LASSERT_FUZZY(expr1, expr2, tolerance) l::logging::LogMessage(__FILE__, __LINE__, l::logging::LogLevel::LogAssertion, sqrt((expr1 - expr2)*(expr1 - expr2)) < tolerance) + +#define LEXPECT(condition) l::logging::LogMessage(__FILE__, __LINE__, l::logging::LogLevel::LogExpection, condition) + template T* require(T* ptr) { if (!(ptr)) { DEBUG_BREAK; } \ @@ -112,3 +118,6 @@ T* require(T* ptr) { #define REQUIRE(ptr) \ require(ptr); +#define LREQUIRE(ptr) \ + require(ptr); + diff --git a/packages/logging/include/logging/Macro.h b/packages/logging/include/logging/Macro.h index f989e1fe..7a1ebd4b 100644 --- a/packages/logging/include/logging/Macro.h +++ b/packages/logging/include/logging/Macro.h @@ -2,12 +2,14 @@ #define CONCATE_(X,Y) X##Y +#define CONCATE_3(X,Y,Z) X##Y##Z +#define CONCATE_4(X,Y,Z,W) X##Y##Z##W #define CONCATE(X,Y) CONCATE_(X,Y) -#define CONCATE3(X,Y,Z) CONCATE_(X,CONCATE(Y, Z)) -#define CONCATE4(X,Y,Z,W) CONCATE_(X,CONCATE3(Y, Z, W)) +#define CONCATE3(X,Y,Z) CONCATE_3(X,Y,Z) +#define CONCATE4(X,Y,Z,W) CONCATE_4(X,Y,Z,W) #define STRINGIFY(X) #X -#define UNIQUE(NAME) CONCATE(NAME, __LINE__) +#define UNIQUE(NAME) CONCATE3(NAME, __LINE__, __COUNTER__) #define UNUSED(symbol) (void)(symbol) diff --git a/packages/logging/include/logging/Static.h b/packages/logging/include/logging/Static.h index c2be1312..38d7b635 100644 --- a/packages/logging/include/logging/Static.h +++ b/packages/logging/include/logging/Static.h @@ -17,7 +17,7 @@ STATIC { .. statement }; */ -#define STATIC static l::Static_ UNIQUE(block) = [&]() -> void +#define STATIC_BLOCK static l::Static_ UNIQUE(block) = [&]() -> void /* Usage: STATIC_CALL([]() { diff --git a/packages/logging/include/logging/String.h b/packages/logging/include/logging/String.h index aeafea11..ce0e40e7 100644 --- a/packages/logging/include/logging/String.h +++ b/packages/logging/include/logging/String.h @@ -34,7 +34,9 @@ namespace l::string { template class string_buffer { public: - string_buffer() = default; + string_buffer() { + memset(mBuf, 0, BUFSIZE); + } ~string_buffer() = default; void pos(int32_t p) { @@ -42,10 +44,24 @@ namespace l::string { } void clear() { + mBuf[0] = 0; mPos = 0; cur() = 0; } + bool empty() { + return size() == 0; + } + + void flush() { + for (size_t i = 0; i < BUFSIZE; i++) { + if (mBuf[i] == 0) { + return; + } + mPos++; + } + } + size_t left() { return static_cast(BUFSIZE - mPos); } @@ -76,6 +92,13 @@ namespace l::string { return std::string_view( &mBuf[0], size()); } + char* data() { + return &mBuf[0]; + } + + size_t capacity() { + return BUFSIZE - 1; + } protected: int32_t mPos = 0; char mBuf[BUFSIZE]; @@ -153,29 +176,29 @@ namespace l::string { struct std::tm tminfo = {}; convert_to_local_tm_from_utc_time(unixtime, &tminfo, false); if (fullYear) { - buf.printf("%4d-%2d-%2d", tminfo.tm_year, tminfo.tm_mon + 1, tminfo.tm_mday); + buf.printf("%04d-%02d-%02d", tminfo.tm_year, tminfo.tm_mon + 1, tminfo.tm_mday); } else { - buf.printf("%4d-%2d-%2d", tminfo.tm_year, tminfo.tm_mon + 1, tminfo.tm_mday); + buf.printf("%04d-%02d-%02d", tminfo.tm_year, tminfo.tm_mon + 1, tminfo.tm_mday); } } - template + template void get_local_time(string_buffer& buf, const int32_t unixtime) { struct std::tm tminfo = {}; convert_to_local_tm_from_utc_time(unixtime, &tminfo, false); - buf.printf("%2d:%2d:%2d", tminfo.tm_hour, tminfo.tm_min, tminfo.tm_sec); + buf.printf("%02d:%02d:%02d", tminfo.tm_hour, tminfo.tm_min, tminfo.tm_sec); } - template + template void get_local_date_and_time(string_buffer& buf, const int32_t unixtime, bool fullYear = false) { struct std::tm tminfo = {}; - convert_to_local_tm_from_utc_time(unixtime, &tminfo, false); + convert_to_local_tm_from_utc_time(unixtime, &tminfo, true); if (fullYear) { - buf.printf("%4d-%2d-%2d %2d:%2d:%2d", tminfo.tm_year, tminfo.tm_mon + 1, tminfo.tm_mday, tminfo.tm_hour, tminfo.tm_min, tminfo.tm_sec); + buf.printf("%04d-%02d-%02d %02d:%02d:%02d", tminfo.tm_year, tminfo.tm_mon + 1, tminfo.tm_mday, tminfo.tm_hour, tminfo.tm_min, tminfo.tm_sec); } else { - buf.printf("%2d-%2d-%2d %2d:%2d:%2d", tminfo.tm_year, tminfo.tm_mon + 1, tminfo.tm_mday, tminfo.tm_hour, tminfo.tm_min, tminfo.tm_sec); + buf.printf("%02d-%02d-%02d %02d:%02d:%02d", tminfo.tm_year, tminfo.tm_mon + 1, tminfo.tm_mday, tminfo.tm_hour, tminfo.tm_min, tminfo.tm_sec); } } @@ -215,6 +238,9 @@ namespace l::string { bool equal_partial(std::string_view a, std::string_view b, size_t a_offset = 0, size_t b_offset = 0); int32_t equal_anywhere(std::string_view a, std::string_view b); + bool is_numeric(char c); + bool is_letter(char c, bool lowercase = true); + std::vector split(std::wstring_view text, std::wstring_view delim = L" \t\n", char escapeChar = '\"'); std::vector split(std::string_view text, std::string_view delim = " \t\n", char escapeChar = '\"'); @@ -222,6 +248,7 @@ namespace l::string { std::wstring widen(const std::string& str); int count_digits(int number); + std::tuple to_fixed_int(std::string_view s); template concept Number = requires(T a) { requires std::convertible_to || std::convertible_to; }; @@ -314,5 +341,78 @@ namespace l::string { std::string hex_encode(std::string_view str); std::string hex_decode(std::string_view str); + + template >> + void clear_flags(I& allflags, const I flags) { + allflags &= ~flags; + } + + template >> + void set_flags(I& allflags, const I flags) { + allflags |= flags; + } + + template >> + bool has_flags(const I allflags, const I flags) { + return (allflags & flags) == flags; + } + + template + void format_float(l::string::string_buffer& out, float value) { + auto onlypositive = value < 0.0f ? -value : value; + + char format[7] = "%7.7f"; + + auto numdecimals = 0; + + if (onlypositive > 100000.0) { + numdecimals = 0; + } + else if (onlypositive > 10000.0) { + numdecimals = 1; + } + else if (onlypositive > 1000.0) { + numdecimals = 2; + } + else if (onlypositive > 100.0) { + numdecimals = 3; + } + else if (onlypositive > 10.0) { + numdecimals = 4; + } + else if (onlypositive > 1.0) { + numdecimals = 5; + } + else if (onlypositive > 0.1) { + numdecimals = 5; + } + else if (onlypositive > 0.01) { + numdecimals = 6; + } + else if (onlypositive > 0.001) { + numdecimals = 7; + } + else if (onlypositive > 0.0001) { + numdecimals = 8; + } + else if (onlypositive > 0.00001) { + numdecimals = 8; + } + else if (onlypositive > 0.000001) { + numdecimals = 8; + } + else if (onlypositive > 0.0) { + numdecimals = numdecimals < 8 ? numdecimals : 8; + } + else { + numdecimals = numdecimals < 4 ? numdecimals : 4; + } + + auto numnumbers = 9 - numdecimals; + format[1] = '0' + static_cast(numnumbers); + format[3] = '0' + static_cast(numdecimals); + + out.printf(format, value); + } } diff --git a/packages/logging/source/common/String.cpp b/packages/logging/source/common/String.cpp index 8a17dd72..54f572ce 100644 --- a/packages/logging/source/common/String.cpp +++ b/packages/logging/source/common/String.cpp @@ -49,8 +49,9 @@ namespace l::string { init_timezone(); #ifdef WIN32 long time; - auto res = _get_timezone(&time); - ASSERT(res == 0); + //auto res = + _get_timezone(&time); + //ASSERT(res == 0); #else auto time = timezone; #endif @@ -61,8 +62,9 @@ namespace l::string { init_timezone(); #ifdef WIN32 int time; - auto res = _get_daylight(&time); - ASSERT(res == 0); + //auto res = + _get_daylight(&time); + //ASSERT(res == 0); #else auto time = daylight; #endif @@ -99,8 +101,9 @@ namespace l::string { void convert_to_tm(const time_t time, tm* timeinfo, bool adjustYearAndMonth) { #ifdef WIN32 - auto res = _gmtime64_s(timeinfo, &time); - ASSERT(res == 0); + //auto res = + _gmtime64_s(timeinfo, &time); + //ASSERT(res == 0); #else tm* ti = gmtime(&time); *timeinfo = *ti; @@ -171,7 +174,7 @@ namespace l::string { int ret = 0; if (date.size() > 10) { - ASSERT(date.size() == 19); + //ASSERT(date.size() == 19); #ifdef WIN32 ret = sscanf_s(date.data(), "%4d-%2d-%2d %2d:%2d:%2d", &timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday, &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec); @@ -182,7 +185,7 @@ namespace l::string { ASSERT(ret <= 6); } else { - ASSERT(date.size() == 10); + //ASSERT(date.size() == 10); #ifdef WIN32 ret = sscanf_s(date.data(), "%4d-%2d-%2d", &timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday); @@ -208,7 +211,7 @@ namespace l::string { int ret = 0; int microsec; - ASSERT(date.size() == 28); + //ASSERT(date.size() == 28); #ifdef WIN32 ret = sscanf_s(date.data(), "%4d-%2d-%2dT%2d:%2d:%2d.%7dZ", &timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday, &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec, µsec); @@ -266,7 +269,7 @@ namespace l::string { int32_t to_unix_time_local2(std::string_view dateAndTime) { struct tm timeinfo = {}; int microsec; - ASSERT(dateAndTime.size() == 28); + //ASSERT(dateAndTime.size() == 28); #ifdef WIN32 int ret = sscanf_s(dateAndTime.data(), "%4d-%2d-%2dT%2d:%2d:%2d.%7dZ", &timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday, &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec, µsec); @@ -441,6 +444,18 @@ namespace l::string { return -1; } + bool is_numeric(char c) { + return c >= '0' && c <= '9'; + } + bool is_letter(char c, bool lowercase) { + if (lowercase) { + return c >= 'a' && c <= 'z'; + } + else { + return c >= 'A' && c <= 'Z'; + } + } + std::vector split(std::wstring_view text, std::wstring_view delim, char escapeChar) { std::vector out; @@ -514,7 +529,7 @@ namespace l::string { std::locale loc; auto size = str.length(); - EXPECT(size > 0 && size < buffer_size) << "Failed to narrow string of size " << size << " characters"; + //EXPECT(size > 0 && size < buffer_size) << "Failed to narrow string of size " << size << " characters"; auto str_ptr = str.data(); @@ -529,7 +544,7 @@ namespace l::string { std::locale loc(""); auto size = str.length(); - EXPECT(size > 0 && size < buffer_size) << "Failed to widen string of size " << size << " characters"; + //EXPECT(size > 0 && size < buffer_size) << "Failed to widen string of size " << size << " characters"; auto str_ptr = str.data(); @@ -547,6 +562,29 @@ namespace l::string { return i; } + std::tuple to_fixed_int(std::string_view s) { + int64_t n = 0; + int32_t numDecimals = -1; + int32_t numDigits = -1; + for (char c : s) { + int64_t digit = static_cast(c - '0'); + if (digit < 0 || digit > 9) { + if (c == '.') { + numDecimals = 0; + } + continue; + } + if (digit > 0) { + numDigits = 0; + } + numDecimals = numDecimals < 0 ? numDecimals : numDecimals + 1; + numDigits = numDigits < 0 ? numDigits : numDigits + 1; + n *= 10; + n += digit; + } + return std::tuple(n, numDecimals, numDigits); + } + std::string_view cut(std::string_view s, const char ch) { auto i = s.find(ch); if (i != std::string::npos) { @@ -623,7 +661,7 @@ namespace l::string { out = 10 + (c - 97); } else { - ASSERT(false); + //ASSERT(false); out = 0; } return mostSignificant ? out << 4 : out; @@ -661,7 +699,7 @@ namespace l::string { out = 10 + (c - 97); } else { - ASSERT(false); + //ASSERT(false); out = 0; } return mostSignificant ? out << 4 : out; diff --git a/packages/math/CMakeLists.txt b/packages/math/CMakeLists.txt index dcd2c725..3d657915 100644 --- a/packages/math/CMakeLists.txt +++ b/packages/math/CMakeLists.txt @@ -3,7 +3,6 @@ project (math) set(deps logging testing - memory ) bs_generate_package(math "tier1" "${deps}" "") diff --git a/packages/math/include/math/MathAlgorithm.h b/packages/math/include/math/MathAlgorithm.h index 5e37be63..0183db7a 100644 --- a/packages/math/include/math/MathAlgorithm.h +++ b/packages/math/include/math/MathAlgorithm.h @@ -1,7 +1,10 @@ #pragma once #include +#include #include +#include +#include #include #include #include @@ -36,13 +39,13 @@ namespace l::math::algorithm { d.sub(v); }; - template - uint32_t binary_search(const std::vector& elements, const T& data, int32_t minIndex = 1, int32_t maxIndex = INT32_MAX) { + template>::value || std::is_same>::value || std::is_same>::value>> + uint32_t binary_search(const S& elements, const T& data, int32_t minIndex = 1, int32_t maxIndex = INT32_MAX) { uint32_t L = static_cast(minIndex < 0 ? 0 : minIndex); uint32_t R = static_cast((maxIndex < elements.size() ? maxIndex : elements.size()) - 1); while (L <= R) { - uint32_t m = static_cast(floor((L + R) / 2.0)); + uint32_t m = static_cast(math::floor((L + R) / 2.0)); auto& e = elements.at(static_cast(m)); if (e < data) { L = m + 1; @@ -57,6 +60,60 @@ namespace l::math::algorithm { return 0; } + template>::value || std::is_same>::value || std::is_same>::value>> + int32_t binary_search_leq(const S& elements, const T& data, int32_t minIndex = 0, int32_t maxIndex = INT32_MAX) { + int32_t left = static_cast(minIndex < 0 ? 0 : minIndex); + int32_t right = static_cast((maxIndex < elements.size() ? maxIndex : elements.size()) - 1); + int32_t result = -1; // Default if no element is <= value + + while (left <= right) { + int32_t mid = left + (right - left) / 2; + + if (elements.at(mid) <= data) { + result = mid; // Valid candidate found + left = mid + 1; // Look for a better candidate to the right + } + else { + right = mid - 1; // Look to the left + } + } + return result; + } + + template>::value || std::is_same>::value || std::is_same>::value>> + int32_t binary_search_fn(const S& elements, std::function fn, bool defaultDirectionRight = true, int32_t minIndex = 0, int32_t maxIndex = INT32_MAX) { + int32_t left = static_cast(minIndex < 0 ? 0 : minIndex); + int32_t right = static_cast((maxIndex < elements.size() ? maxIndex : elements.size()) - 1); + int32_t result = -1; // Default if no element is <= value + + while (left <= right) { + int32_t mid = left + (right - left) / 2; + + auto direction = fn(elements.at(mid)); + if (direction > 0) { + left = mid + 1; // Look to the right + } + else if (direction < 0){ + right = mid - 1; // Look to the left + } + else { + result = mid; // Valid candidate found + if (defaultDirectionRight) { + left = mid + 1; // Look for a better candidate to the right + } + else { + right = mid - 1; // Look for a better candidate to the left + } + } + } + if (result < 0) { + if (left >= elements.size()) { + return elements.size(); + } + } + return result; + } + template T bisect(T a, T b, T tolerance, int iterations, std::function eval) { int n = 0; @@ -65,7 +122,7 @@ namespace l::math::algorithm { c = (a + b) / 2.0; T cEval = eval(c); - LOG(LogDebug) << "bisect iteration " << n << "(" << iterations << ") val: " << c << "(convergence: " << cEval << ")"; + LLOG(LogDebug) << "bisect iteration " << n << "(" << iterations << ") val: " << c << "(convergence: " << cEval << ")"; if (cEval == 0.0 || abs(cEval) < tolerance) { return c; } diff --git a/packages/math/include/math/MathAll.h b/packages/math/include/math/MathAll.h index abbee162..f2683de0 100644 --- a/packages/math/include/math/MathAll.h +++ b/packages/math/include/math/MathAll.h @@ -5,3 +5,4 @@ #include "math/MathConstants.h" #include "math/MathAlgorithm.h" #include "math/MathTween.h" +#include "math/MathFixedPoint.h" diff --git a/packages/math/include/math/MathFixedPoint.h b/packages/math/include/math/MathFixedPoint.h new file mode 100644 index 00000000..1125cc04 --- /dev/null +++ b/packages/math/include/math/MathFixedPoint.h @@ -0,0 +1,146 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace l::math::fp { + + class FixedPoint { + public: + static FixedPoint FromDouble(double d, int64_t scale) { + return FixedPoint(static_cast(l::math::round(d * scale)), scale); + } + + static FixedPoint FromFloat(float f, int64_t scale) { + return FixedPoint(static_cast(l::math::round(f * scale)), scale); + } + + static FixedPoint FromString(std::string_view number, int64_t scale) { + return FromDouble(std::atof(number.data()), scale); + } + + static int64_t GetMinScaleFromDouble(double scaleDouble) { + if (scaleDouble == 0.0) { + return 0; + } + int32_t numDecimals = 0; + while (scaleDouble < 1.0 && numDecimals < 18) { + scaleDouble *= 10.0; + ++numDecimals; + } + if (numDecimals > 18) return false; // avoid int64_t overflow + auto scale = static_cast(0.5f + l::math::pow(10.0f, static_cast(numDecimals))); + return scale; + } + + FixedPoint(); + explicit FixedPoint(int64_t scaledValue, int64_t scale); + explicit FixedPoint(double value, int32_t numdecimals, bool floorValue = false); + explicit FixedPoint(double value, bool floorValue = false); + explicit FixedPoint(float value, int32_t numdecimals, bool floorValue = false); + explicit FixedPoint(float value, bool floorValue = false); + FixedPoint(std::string_view number, bool floorValue = false); + + int32_t numDigits() const; + double toDouble() const; + float toFloat() const; + std::string toString() const; + + template + void getString(l::string::string_buffer& buf) const { + int64_t int_part = value_ / scale_; + int64_t frac_part = l::math::abs(value_ % scale_); + + buf.printf("%lld", int_part); + + if (scale_ > 1) { + int decimal_digits = static_cast(l::math::logx(10.0f, static_cast(scale_))); + buf.printf(".%0*d", decimal_digits, frac_part); + } + } + + void normalise(); + void rescale(int64_t scale, bool truncate = false); + void round(int32_t numDecimals); + void floor(int32_t numDecimals); + + // Arithmetic + FixedPoint operator+(const FixedPoint& other) const { + FixedPoint tmp(other); + tmp.rescale(scale_); + return FixedPoint(value_ + tmp.value_, scale_); + } + + FixedPoint operator-(const FixedPoint& other) const { + FixedPoint tmp(other); + tmp.rescale(scale_); + return FixedPoint(value_ - tmp.value_, scale_); + } + + FixedPoint operator*(const FixedPoint& other) const { + if (other.scale() != scale()) { + FixedPoint tmp(other); + tmp.rescale(scale_); + return FixedPoint(value_ * tmp.value_, scale_ * scale_); + } + return FixedPoint(value_ * other.value_, scale_ * scale_); + } + + FixedPoint operator*(int64_t multiplier) const { + return FixedPoint(value_ * multiplier, scale_); + } + + bool operator==(const FixedPoint& other) const { + FixedPoint tmp(other); + tmp.rescale(scale_); + return value_ == tmp.value_ && scale_ == tmp.scale_; + } + + bool operator!=(const FixedPoint& other) const { + return !(*this == other); + } + + bool operator<(const FixedPoint& other) const { + FixedPoint tmp(other); + tmp.rescale(scale_); + return value_ < tmp.value_; + } + + bool operator>(const FixedPoint& other) const { + return other < *this; + } + + bool operator<=(const FixedPoint& other) const { + return !(*this > other); + } + + bool operator>=(const FixedPoint& other) const { + return !(*this < other); + } + + FixedPoint operator%(const FixedPoint& other) const { + FixedPoint tmp(other); + tmp.rescale(scale_); + return FixedPoint(value_ % tmp.value_, scale_); + } + + // Getters + int64_t rawValue() const { return value_; } + int64_t scale() const { return scale_; } + + private: + int64_t value_ = 0; + int64_t scale_ = 1; // per-instance scale (e.g., 1e8) + + }; + + +} diff --git a/packages/math/include/math/MathFunc.h b/packages/math/include/math/MathFunc.h index a4bb6fb2..abfe2ce7 100644 --- a/packages/math/include/math/MathFunc.h +++ b/packages/math/include/math/MathFunc.h @@ -47,12 +47,12 @@ namespace l::math { template auto min3(T val1, T val2, T val3) { - return val1 < val2 ? (val3 < val1 ? val3 : val1) : val2; + return val1 < val2 ? (val3 < val1 ? val3 : val1) : (val3 < val2 ? val3 : val2); } template auto max3(T val1, T val2, T val3) { - return val1 > val2 ? (val1 > val3 ? val1 : val3) : val2; + return val1 > val2 ? (val1 > val3 ? val1 : val3) : (val1 > val2 ? val1 : val2); } template @@ -230,6 +230,29 @@ namespace l::math { } } + template + auto trunc(T val) { + if constexpr (std::is_floating_point_v) { + if constexpr (sizeof(T) == 4) { + return truncf(val); + } + else if constexpr (sizeof(T) == 8) { + return truncl(val); + } + } + } + + template + V trunc(T val) { + if constexpr (std::is_floating_point_v) { + if constexpr (sizeof(T) == 4) { + return static_cast(truncf(val)); + } + else if constexpr (sizeof(T) == 8) { + return static_cast(truncl(val)); + } + } + } template auto log(T val) { if constexpr (std::is_floating_point_v) { @@ -242,6 +265,11 @@ namespace l::math { } } + template + auto logx(T base, T val) { + return log(val) / log(base); + } + // Sinc curve // A phase shifted sinc curve can be useful if it starts at zeroand ends at zero, for some bouncing behaviors(suggested by Hubert - Jan).Give k different integer values to tweak the amount of bounces.It peaks at 1.0, but that take negative values, which can make it unusable in some applications. template @@ -398,6 +426,7 @@ namespace l::math::functions { return k * math::pow(x, a) * math::pow(1.0 - x, b); } + // Sigmoid function maps the input to the interval [0,1] for [0,inf] input ([1] maps to [0.5]). template auto sigmoid(T x, T k) { return static_cast(static_cast(1.0) / (static_cast(1.0) + math::exp(-x * k))); @@ -412,4 +441,10 @@ namespace l::math::functions { return static_cast(x > static_cast(0.0) ? static_cast(1.0) : static_cast(-1.0)); } } + + template + float infUnit(T x, T cutoff) { + return 1.0 / (1.0 + math::pow(2.71828, - x * 5.0 / cutoff)); + } + } diff --git a/packages/math/source/common/MathFixedPoint.cpp b/packages/math/source/common/MathFixedPoint.cpp new file mode 100644 index 00000000..1d58eef7 --- /dev/null +++ b/packages/math/source/common/MathFixedPoint.cpp @@ -0,0 +1,160 @@ + +#include "math/MathFixedPoint.h" + +namespace l::math::fp { + + + FixedPoint::FixedPoint() : value_(0), scale_(1) {} + + FixedPoint::FixedPoint(int64_t scaledValue, int64_t scale) + : value_(scaledValue), scale_(scale) { + normalise(); + } + + FixedPoint::FixedPoint(double value, int32_t numdecimals, bool floorValue) { + auto v = l::math::abs(value); + if (v < 1.0) { + scale_ = 1000000000000000000; + value_ = static_cast(value * scale_); + } + else if (v < 1000000000.0) { + scale_ = 1000000000; + value_ = static_cast(value * scale_); + } + else { + scale_ = 1; + value_ = static_cast(value * scale_); + } + if (!floorValue) { + round(numdecimals); + } + else { + floor(numdecimals); + } + } + + FixedPoint::FixedPoint(double value, bool floorValue) { + auto v = l::math::abs(value); + auto numdecimals = 0; + if (v < 1.0) { + scale_ = 1000000000000000000; + value_ = static_cast(value * scale_); + numdecimals = 9; + } + else if (v < 1000000000.0) { + scale_ = 1000000000; + value_ = static_cast(value * scale_); + numdecimals = 9; + } + else { + scale_ = 1; + value_ = static_cast(value * scale_); + numdecimals = 0; + } + if (!floorValue) { + round(numdecimals); + } + else { + floor(numdecimals); + } + } + + FixedPoint::FixedPoint(float value, int32_t numdecimals, bool floorValue) : FixedPoint(static_cast(value), numdecimals, floorValue) { + } + + FixedPoint::FixedPoint(float value, bool floorValue) : FixedPoint(static_cast(value), floorValue) { + } + + FixedPoint::FixedPoint(std::string_view number, bool floorValue) { + auto [n, d, s] = l::string::to_fixed_int(number); + value_ = n; + auto scale = static_cast(0.5f + l::math::pow(10.0f, static_cast(d))); + scale_ = scale; + + if (!floorValue) { + round(d); + } + else { + floor(d); + } + } + + void FixedPoint::normalise() { + while (scale_ >= 10 && value_ >= 10 && (value_ % 10) == 0) { + value_ = value_ / 10; + scale_ = scale_ / 10; + } + } + + void FixedPoint::rescale(int64_t newScale, bool truncate) { + if (newScale > scale_) { // scale up, no loss in precision + int64_t diff = newScale / scale_; + //ASSERT(l::math::abs(value_ * diff) < 100000000000000000); + value_ *= diff; + scale_ *= diff; + } + else if (newScale < scale_) { + int64_t diff = scale_ / newScale; + //ASSERT(diff >= 10); + scale_ /= diff; + if (!truncate) { + diff /= 10; + } + value_ /= diff; + if (!truncate) { + value_ += 5; // add 0.5 before floor + value_ /= 10; // round (floor(0.5 + x)) + } + } + } + + void FixedPoint::round(int32_t numDecimals) { + int64_t scale = 1; + while (numDecimals-- > 0) { + scale *= 10; + }; + rescale(scale); + normalise(); + } + + void FixedPoint::floor(int32_t numDecimals) { + int64_t scale = 1; + while (numDecimals-- > 0) { + scale *= 10; + }; + rescale(scale, true); + normalise(); + } + + int32_t FixedPoint::numDigits() const { + return static_cast(l::math::logx(10.0f, static_cast(scale_))); + } + + double FixedPoint::toDouble() const { + return static_cast(value_) / static_cast(scale_); + } + + float FixedPoint::toFloat() const { + return static_cast(toDouble()); + } + + std::string FixedPoint::toString() const { + std::ostringstream oss; + + int64_t int_part = value_ / scale_; + int64_t frac_part = l::math::abs(value_ % scale_); + + oss << int_part; + + if (scale_ > 1) { + // Determine number of digits in the scale (e.g., 100000000 -> 8) + int decimal_digits = static_cast(l::math::logx(10.0f, static_cast(scale_))); + + // Pad with leading zeros if necessary + oss << "." << std::setw(decimal_digits) << std::setfill('0') << frac_part; + } + + return oss.str(); + } + +} diff --git a/packages/math/tests/common/MathFixedPointTest.cpp b/packages/math/tests/common/MathFixedPointTest.cpp new file mode 100644 index 00000000..53d63c49 --- /dev/null +++ b/packages/math/tests/common/MathFixedPointTest.cpp @@ -0,0 +1,129 @@ +#include "testing/Test.h" +#include "logging/Log.h" + +#include "math/MathAll.h" + +using namespace l; +using namespace l::math::fp; + +TEST(MathFixedPoint, Basic) { + { + FixedPoint fp(100300001.12); + TEST_TRUE(fp.scale() == 100, ""); + TEST_FUZZY(fp.toDouble(), 100300001.12, 0.001, ""); + TEST_TRUE(fp.rawValue() == 10030000112, ""); + } + { + FixedPoint fp(100300001000016.0); + TEST_TRUE(fp.scale() == 1, ""); + TEST_FUZZY(fp.toDouble(), 100300001000016.0, 0.01, ""); + TEST_TRUE(fp.rawValue() == 100300001000016, ""); + } + { + FixedPoint fp(1000.1f, 0); + TEST_TRUE(fp.scale() == 1, ""); + TEST_FUZZY(fp.toFloat(), 1000.0f, 0.0000001f, ""); + TEST_TRUE(fp.rawValue() == 1000, ""); + } + { + FixedPoint fp(1000.1f, 1); + TEST_TRUE(fp.scale() == 10, ""); + TEST_FUZZY(fp.toFloat(), 1000.1f, 0.0000001f, ""); + TEST_TRUE(fp.rawValue() == 10001, ""); + } + { + FixedPoint fp(1001.1f, 1); + TEST_TRUE(fp.scale() == 10, ""); + TEST_FUZZY(fp.toFloat(), 1001.1f, 0.0000001f, ""); + TEST_TRUE(fp.rawValue() == 10011, ""); + } + { + FixedPoint fp(0.0001f); + TEST_TRUE(fp.scale() == 10000, ""); + TEST_FUZZY(fp.toFloat(), 0.0001f, 0.0000001f, ""); + TEST_TRUE(fp.rawValue() == 1, ""); + } + { + FixedPoint fp(0.00000004f); + TEST_TRUE(fp.scale() == 100000000, ""); + TEST_FUZZY(fp.toFloat(), 0.00000004f, 0.00000001f, ""); + TEST_TRUE(fp.rawValue() == 4, ""); + } + { + FixedPoint fp(0.002f); + TEST_TRUE(fp.scale() == 1000, ""); + TEST_FUZZY(fp.toFloat(), 0.002f, 0.0000001f, ""); + TEST_TRUE(fp.rawValue() == 2, ""); + } + { + FixedPoint fp(0.00213f); + TEST_TRUE(fp.scale() == 100000, ""); + TEST_FUZZY(fp.toFloat(), 0.00213f, 0.0000001f, ""); + TEST_TRUE(fp.rawValue() == 213, ""); + } + + { + FixedPoint fp("0.002"); + TEST_TRUE(fp.scale() == 1000, ""); + TEST_FUZZY(fp.toFloat(), 0.002f, 0.0000001f, ""); + TEST_TRUE(fp.rawValue() == 2, ""); + } + { + FixedPoint fp("0.00213"); + TEST_TRUE(fp.scale() == 100000, ""); + TEST_FUZZY(fp.toFloat(), 0.00213f, 0.0000001f, ""); + TEST_TRUE(fp.rawValue() == 213, ""); + } + { + FixedPoint fp("0.001f"); + TEST_TRUE(fp.scale() == 1000, ""); + TEST_FUZZY(fp.toFloat(), 0.001f, 0.0000001f, ""); + TEST_TRUE(fp.rawValue() == 1, ""); + } + { + FixedPoint fp("10000.0f"); + TEST_TRUE(fp.scale() == 1, ""); + TEST_FUZZY(fp.toFloat(), 10000.0f, 0.0000001f, ""); + TEST_TRUE(fp.rawValue() == 10000, ""); + } + return 0; +} + + +TEST(MathFixedPoint, ToString) { + + { + l::string::string_buffer<20> buf; + FixedPoint fp("124.005154"); + fp.getString(buf); + TEST_TRUE(fp.toString() == buf.str(), ""); + LLOG(LogTest) << buf.str(); + } + { + l::string::string_buffer<20> buf; + FixedPoint fp("0.000000005154"); + fp.getString(buf); + TEST_TRUE(fp.toString() == buf.str(), ""); + + LLOG(LogTest) << buf.str(); + } + { + l::string::string_buffer<20> buf; + FixedPoint fp("0.000000005154", true); + fp.getString(buf); + TEST_TRUE(fp.toString() == buf.str(), ""); + + LLOG(LogTest) << buf.str(); + } + { + l::string::string_buffer<20> buf; + FixedPoint fp("1256.33204"); + fp.getString(buf); + TEST_TRUE(fp.toString() == buf.str(), ""); + + LLOG(LogTest) << buf.str(); + } + return 0; +} + + diff --git a/packages/math/tests/common/MathTweenTest.cpp b/packages/math/tests/common/MathTweenTest.cpp index 576e6a62..febd0b65 100644 --- a/packages/math/tests/common/MathTweenTest.cpp +++ b/packages/math/tests/common/MathTweenTest.cpp @@ -15,7 +15,7 @@ TEST(MathTween, RecentWeightedAverage) { TEST_FUZZY(rwa.Value(), 1.0f, 0.1f, ""); for (int i = 0; i < 10; i++) { auto value = rwa.Next(); - LOG(LogInfo) << "RWA: " << value; + LLOG(LogInfo) << "RWA: " << value; } TEST_FUZZY(rwa.Value(), 0.0f, 0.1f, ""); @@ -30,7 +30,7 @@ TEST(MathTween, DynamicTween) { for (int i = 0; i < 10; i++) { tween.Update(); auto value = tween.Next(); - LOG(LogInfo) << "Tween: " << value; + LLOG(LogInfo) << "Tween: " << value; } TEST_FUZZY(tween.Value(), 0.0f, 0.01f, ""); @@ -47,7 +47,7 @@ TEST(MathTween, DynamicTweenVeryShortBatch) { tween.Update(10); } auto value = tween.Next(); - LOG(LogInfo) << "Tween (" << i << "): " << value; + LLOG(LogInfo) << "Tween (" << i << "): " << value; } TEST_FUZZY(tween.Value(), 0.0f, 0.01f, ""); @@ -64,7 +64,7 @@ TEST(MathTween, DynamicTweenShortBatch) { tween.Update(10); } auto value = tween.Next(); - LOG(LogInfo) << "Tween (" << i << "): " << value; + LLOG(LogInfo) << "Tween (" << i << "): " << value; } TEST_FUZZY(tween.Value(), 0.0f, 0.01f, ""); @@ -82,7 +82,7 @@ TEST(MathTween, DynamicTweenLongBatch) { tween.Update(updateRate); } auto value = tween.Next(); - LOG(LogInfo) << "Tween (" << i << "): " << value; + LLOG(LogInfo) << "Tween (" << i << "): " << value; } TEST_FUZZY(tween.Value(), 0.0f, 0.02f, ""); diff --git a/packages/memory/include/memory/Containers.h b/packages/memory/include/memory/Containers.h index 9c16cd3a..f5f09180 100644 --- a/packages/memory/include/memory/Containers.h +++ b/packages/memory/include/memory/Containers.h @@ -17,7 +17,7 @@ namespace l::container { template std::vector vector_extract(std::vector& v, size_t i, size_t count) { - ASSERT(i < v.size() && count <= v.size()); + //ASSERT(i < v.size() && count <= v.size()); std::vector dst; dst.insert(dst.end(), std::make_move_iterator(v.begin() + i), std::make_move_iterator(v.begin() + i + count)); v.erase(v.begin() + i, v.begin() + i + count); diff --git a/packages/memory/include/memory/VectorSorted.h b/packages/memory/include/memory/VectorSorted.h index a8e71bdb..f1e069ef 100644 --- a/packages/memory/include/memory/VectorSorted.h +++ b/packages/memory/include/memory/VectorSorted.h @@ -54,6 +54,14 @@ namespace l::container { return it; } + typename std::vector::iterator upper_bound_it(const T& key) { + if (!m_bSorted) + sort(); + + typename std::vector::iterator it = std::upper_bound(vec.begin(), vec.end(), key); + return it; + } + /*const*/ T* lower_bound_ptr(const T& key) { typename std::vector::iterator it = lower_bound_it(key); @@ -101,6 +109,23 @@ namespace l::container { return vec.end(); } + typename std::vector::iterator find_at_or_below(const T& key) { + typename std::vector::iterator it = upper_bound_it(key); + + if (it == vec.begin()) // first element is greater than key + return vec.end(); + if (!vec.empty()) // key is greater than all elements so get the last one + return it - 1; + return vec.end(); + } + typename std::vector::iterator find_at_or_above(const T& key) { + typename std::vector::iterator it = lower_bound_it(key); + + if (it != vec.end()) + return it; + + return vec.end(); + } //-------------------------------------------------------------------// // find_ptr_or_fail() // //-------------------------------------------------------------------// @@ -242,9 +267,30 @@ namespace l::container { return vec.empty(); } + inline void clear() noexcept { + return vec.clear(); + } + + T& back() noexcept { + if (!m_bSorted) + { + sort(); + } + return vec.back(); + } + + void pop_front(size_t count) { + if (count < vec.size()) { + vec.erase(vec.begin(), vec.begin() + count); + } + else { + clear(); + } + } + protected: std::vector vec; - bool m_bSorted; + bool m_bSorted = true; }; } diff --git a/packages/memory/source/common/Arena.cpp b/packages/memory/source/common/Arena.cpp index e3751aa7..769e88a5 100644 --- a/packages/memory/source/common/Arena.cpp +++ b/packages/memory/source/common/Arena.cpp @@ -63,13 +63,13 @@ namespace l::memory { // pop some bytes off the 'stack' - the way to free void ArenaPop(Arena* arena, uint64_t size) { if (arena->mBlocks.empty()) { - LOG(LogError) << "Trying to pop from an empty arena"; + LLOG(LogError) << "Trying to pop from an empty arena"; return; } MemoryBlock& lastBlock = arena->mBlocks.back(); if (lastBlock.mSize - (reinterpret_cast(lastBlock.mBase) - reinterpret_cast(arena->mBlocks.back().mBase)) < size) { - LOG(LogError) << "Trying to pop more bytes than are available in the current block"; + LLOG(LogError) << "Trying to pop more bytes than are available in the current block"; return; } @@ -89,13 +89,13 @@ namespace l::memory { // also some useful popping helpers: void ArenaSetPosBack(Arena* arena, uint64_t pos) { if (arena->mBlocks.empty()) { - LOG(LogError) << "Trying to set position in an empty arena"; + LLOG(LogError) << "Trying to set position in an empty arena"; return; } MemoryBlock& lastBlock = arena->mBlocks.back(); if (lastBlock.mSize - (reinterpret_cast(lastBlock.mBase) - reinterpret_cast(arena->mBlocks.back().mBase)) < pos) { - LOG(LogError) << "Trying to set position past the end of the current block"; + LLOG(LogError) << "Trying to set position past the end of the current block"; return; } diff --git a/packages/memory/tests/common/ContainersTest.cpp b/packages/memory/tests/common/ContainersTest.cpp index 4f586fd7..bd27d0a8 100644 --- a/packages/memory/tests/common/ContainersTest.cpp +++ b/packages/memory/tests/common/ContainersTest.cpp @@ -5,7 +5,7 @@ TEST(Containers, VectorOps) { { - LOG(LogInfo) << l::string::get_unix_epoch_ms(); + LLOG(LogInfo) << l::string::get_unix_epoch_ms(); std::vector v = { 1, 3, 5, 32, 53, 2, 95 }; auto v_sub3 = l::container::vector_extract(v, 4u, 3u); diff --git a/packages/meta/tests/common/ReflectionTest.cpp b/packages/meta/tests/common/ReflectionTest.cpp index 19d0d25b..4526ffb9 100644 --- a/packages/meta/tests/common/ReflectionTest.cpp +++ b/packages/meta/tests/common/ReflectionTest.cpp @@ -43,12 +43,12 @@ TEST(ReflectionTest, ClassName) { class A {}; class B {}; class C {}; - LOG(LogInfo) << l::meta::Type::name(); - LOG(LogInfo) << l::meta::Type::full_name() << "\n"; - LOG(LogInfo) << l::meta::Type::name() << "\n"; - LOG(LogInfo) << l::meta::Type::full_name() << "\n"; - LOG(LogInfo) << l::meta::Type::name() << "\n"; - LOG(LogInfo) << l::meta::Type::type() << "\n"; + LLOG(LogInfo) << l::meta::Type::name(); + LLOG(LogInfo) << l::meta::Type::full_name() << "\n"; + LLOG(LogInfo) << l::meta::Type::name() << "\n"; + LLOG(LogInfo) << l::meta::Type::full_name() << "\n"; + LLOG(LogInfo) << l::meta::Type::name() << "\n"; + LLOG(LogInfo) << l::meta::Type::type() << "\n"; return 0; } @@ -103,9 +103,9 @@ TEST(ReflectionTest, Polymorphism) { TEST_TRUE(l::meta::Type::hash_code() != l::meta::Type::hash_code(), ""); TEST_TRUE(l::meta::Type::hash_code() != l::meta::Type::hash_code(), ""); - LOG(LogInfo) << l::meta::Type::hash_code(); - LOG(LogInfo) << l::meta::Type::hash_code(); - LOG(LogInfo) << l::meta::Type::hash_code(); + LLOG(LogInfo) << l::meta::Type::hash_code(); + LLOG(LogInfo) << l::meta::Type::hash_code(); + LLOG(LogInfo) << l::meta::Type::hash_code(); } return 0; diff --git a/packages/network/include/network/NetworkConnection.h b/packages/network/include/network/NetworkConnection.h index edfd936b..9ea7bbe4 100644 --- a/packages/network/include/network/NetworkConnection.h +++ b/packages/network/include/network/NetworkConnection.h @@ -81,9 +81,12 @@ namespace l::network { void SetRunningTimeout(int32_t secondsFromNow); void ClearRunningTimeout(); + int32_t WSKeepalive(); int32_t WSWrite(const char* buffer, size_t size); int32_t WSRead(char* buffer, size_t size); void WSClose(); + bool WSAutoConnectEnabled(); + void WSSetAutoConnect(bool autoReconnect); const curl_ws_frame* GetWebSocketMeta(); @@ -120,6 +123,7 @@ namespace l::network { bool mIsWebSocket = false; bool mWebSocketCanReceiveData = false; bool mWebSocketCanSendData = false; + bool mWebSocketAutoConnect = false; }; template diff --git a/packages/network/include/network/NetworkInterfaceWS.h b/packages/network/include/network/NetworkInterfaceWS.h index 39d40f87..414ad92d 100644 --- a/packages/network/include/network/NetworkInterfaceWS.h +++ b/packages/network/include/network/NetworkInterfaceWS.h @@ -33,12 +33,15 @@ namespace l::network { void Disconnect(std::string_view queryName); int32_t Read(std::string_view interfaceName, char* buffer, size_t size); void QueueWrite(std::string_view interfaceName, const char* buffer, size_t size); + int32_t Keepalive(std::string_view interfaceName); int32_t Write(std::string_view interfaceName, const char* buffer, size_t size); void SendQueued(std::string_view interfaceName, int32_t maxQueued); int32_t NumQueued(std::string_view interfaceName); void ClearQueued(std::string_view interfaceName); bool IsConnected(std::string_view interfaceName); + bool IsAutoConnecting(std::string_view queryName); + void SetAutoConnect(std::string_view queryName, bool autoConnect); bool NetworkStatus(std::string_view interfaceName); diff --git a/packages/network/include/network/NetworkManager.h b/packages/network/include/network/NetworkManager.h index 2bc19ab7..ed567bf8 100644 --- a/packages/network/include/network/NetworkManager.h +++ b/packages/network/include/network/NetworkManager.h @@ -6,15 +6,15 @@ #include #include -#include "curl/curl.h" +#include #ifndef CURLPIPE_MULTIPLEX #define CURLPIPE_MULTIPLEX 0 #endif -#include "logging/LoggingAll.h" -#include "concurrency/ExecutorService.h" -#include "network/NetworkConnection.h" +#include +#include +#include namespace l::network { @@ -48,9 +48,12 @@ namespace l::network { std::function cb = nullptr); void WSClose(std::string_view queryName = ""); + int32_t WSKeepalive(std::string_view queryName); int32_t WSWrite(std::string_view queryName, const char* buffer, size_t size); int32_t WSRead(std::string_view queryName, char* buffer, size_t size); bool WSConnected(std::string_view queryName); + bool WSAutoConnectEnabled(std::string_view queryName); + void WSSetAutoConnect(std::string_view queryName, bool autoConnect); protected: std::thread mCurlPerformer; diff --git a/packages/network/source/common/NetworkConnection.cpp b/packages/network/source/common/NetworkConnection.cpp index 99f02be0..387597ee 100644 --- a/packages/network/source/common/NetworkConnection.cpp +++ b/packages/network/source/common/NetworkConnection.cpp @@ -7,7 +7,7 @@ namespace l::network { if (request != nullptr) { request->NotifyClose(); } - LOG(LogDebug) << "Socket close: " << item; + LLOG(LogDebug) << "Socket close: " << item; return 0; } @@ -25,8 +25,8 @@ namespace l::network { if (request->IsWebSocket()) { auto wssMeta = request->GetWebSocketMeta(); if (wssMeta) { - LOG(LogDebug) << "wss meta flags " << wssMeta->flags; - LOG(LogDebug) << "wss meta length " << wssMeta->len; + LLOG(LogDebug) << "wss meta flags " << wssMeta->flags; + LLOG(LogDebug) << "wss meta length " << wssMeta->len; } request->NotifyAppendResponse(contents, size * nmemb); } @@ -40,7 +40,7 @@ namespace l::network { size_t CurlClientProgressCallback(void* clientp, double dltotal, double dlnow, double, double) { auto progress = reinterpret_cast(clientp); double progressTotal = dlnow / dltotal; - LOG(LogDebug) << "downloaded " << progress->size << " bytes" << " and progress at " << std::to_string(int(progressTotal * 100)) << "%"; + LLOG(LogDebug) << "downloaded " << progress->size << " bytes" << " and progress at " << std::to_string(int(progressTotal * 100)) << "%"; return 0; } @@ -69,7 +69,7 @@ namespace l::network { int32_t timeOut, std::function cb ) { - ASSERT(mOngoingRequest) << "Request has not been reserved for usage"; + ASSERT(mOngoingRequest) << "[Request] Request has not been reserved for usage"; ASSERT(!mCompletedRequest); if (mRequestQuery.empty() && query.empty()) { @@ -100,16 +100,24 @@ namespace l::network { SetResponseSize(expectedResponseSize); - curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, mIsWebSocket ? 2L : 0L); + if (mIsWebSocket) { + curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, 2L); + auto res = curl_easy_setopt(mCurl, CURLOPT_WS_OPTIONS, 0L); // 1L - CURLWS_RAW_MODE, 2L - CURLWS_NOAUTOPONG + ASSERT(res == CURLE_OK); + } + else { + curl_easy_setopt(mCurl, CURLOPT_CONNECT_ONLY, 0L); + } + curl_easy_setopt(mCurl, CURLOPT_URL, mRequestQuery.c_str()); - LOG(LogDebug) << mRequestQuery; + LLOG(LogDebug) << mRequestQuery; //curl_easy_setopt(mCurl, CURLOPT_FRESH_CONNECT, 0L); // only use if necessary to create a new connection auto res_verify_peer = curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYPEER, 0L); - ASSERT(res_verify_peer == CURLE_OK) << "Failed to verify peer: " << std::to_string(res_verify_peer); + ASSERT(res_verify_peer == CURLE_OK) << "[Request] Failed to verify peer: " << std::to_string(res_verify_peer); auto res_verify_host = curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYHOST, 0L); - ASSERT(res_verify_host == CURLE_OK) << "Failed to verify host: " << std::to_string(res_verify_host); + ASSERT(res_verify_host == CURLE_OK) << "[Request] Failed to verify host: " << std::to_string(res_verify_host); auto res_ssl = curl_easy_setopt(mCurl, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_ALLOW_BEAST | CURLSSLOPT_NO_REVOKE); - ASSERT(res_ssl == CURLE_OK) << "Failed to set ssl options: " << std::to_string(res_ssl); + ASSERT(res_ssl == CURLE_OK) << "[Request] Failed to set ssl options: " << std::to_string(res_ssl); //curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(mCurl, CURLOPT_CLOSESOCKETFUNCTION, CurlClientCloseSocket); @@ -135,7 +143,7 @@ namespace l::network { #endif auto curlMCode = curl_multi_add_handle(multiHandle, mCurl); if (curlMCode != CURLMcode::CURLM_OK) { - LOG(LogError) << "Curl failure " << std::to_string(curlMCode) << ": " << mRequestQueryArgs; + LLOG(LogError) << "[Request] Curl failure " << std::to_string(curlMCode) << ": " << mRequestQueryArgs; mSuccess = false; } } @@ -143,7 +151,7 @@ namespace l::network { auto curlCode = curl_easy_perform(mCurl); //curl_easy_header() if (curlCode != CURLE_OK) { - LOG(LogError) << "Curl failure " << std::to_string(curlCode) << ": " << mRequestQueryArgs; + LLOG(LogError) << "[Request] Curl failure " << std::to_string(curlCode) << ": " << mRequestQueryArgs; mSuccess = false; } } @@ -250,15 +258,39 @@ namespace l::network { return m; } + int32_t ConnectionBase::WSKeepalive() { + if (HasExpired()) { + mWebSocketCanSendData = false; + LLOG(LogWarning) << "[Keepalive] connection expired"; + return -101; + } + if (mCurl == nullptr) { + mWebSocketCanSendData = false; + LLOG(LogWarning) << "[Keepalive] no curl instance"; + return -102; + } + + const char* payload = "keepalive"; + size_t sentBytes = 0; + auto rc = curl_ws_send(mCurl, payload, strlen(payload), &sentBytes, 0, CURLWS_PONG); + if (rc == CURLE_OK) { + //LLOG(LogInfo) << "[Keepalive] Sent PONG"; + } + else { + LLOG(LogWarning) << "[Keepalive] Failed to send PONG: " << curl_easy_strerror(rc); + } + return rc; + } + int32_t ConnectionBase::WSWrite(const char* buffer, size_t size) { if (HasExpired()) { mWebSocketCanSendData = false; - LOG(LogError) << "Failed wss write, connection expired"; + LLOG(LogWarning) << "[Websocket] Failed write, connection expired"; return -101; } if (mCurl == nullptr) { mWebSocketCanSendData = false; - LOG(LogError) << "Failed wss write, no curl instance"; + LLOG(LogWarning) << "[Websocket] Failed write, no curl instance"; return -102; } size_t sentBytes = 0; @@ -273,13 +305,13 @@ namespace l::network { } else if (res == CURLE_GOT_NOTHING) { if (mWebSocketCanSendData) { - LOG(LogError) << "Failed wss write got nothing, error: " << res; + LLOG(LogWarning) << "[Websocket] Failed write: got nothing, error: " << res; } mWebSocketCanSendData = false; } else { if (mWebSocketCanSendData) { - LOG(LogError) << "Failed wss write, error: " << res; + LLOG(LogWarning) << "[Websocket] Failed write, error: " << res; } mWebSocketCanSendData = false; } @@ -290,20 +322,22 @@ namespace l::network { int32_t ConnectionBase::WSRead(char* buffer, size_t size) { if (HasExpired()) { mWebSocketCanReceiveData = false; - LOG(LogError) << "Failed wss read, connection expired"; + LLOG(LogWarning) << "[Websocket] Failed read, connection expired"; return -101; } if (mCurl == nullptr) { mWebSocketCanReceiveData = false; - LOG(LogError) << "Failed wss read, no curl instance"; + LLOG(LogWarning) << "[Websocket] Failed read, no curl instance"; return -102; } int32_t maxTries = 3; size_t readTotal = 0; CURLcode res = CURLE_OK; + + const struct curl_ws_frame* meta = nullptr; + while (!res) { size_t recv = 0; - const struct curl_ws_frame* meta = nullptr; auto recvMax = size - readTotal; res = curl_ws_recv(mCurl, buffer + readTotal, recvMax, &recv, &meta); readTotal += recv; @@ -314,6 +348,15 @@ namespace l::network { if (meta) { multiFragmentBit = (meta->flags & CURLWS_CONT) == CURLWS_CONT; recvLeft = static_cast(meta->bytesleft); + + if (meta->flags & CURLWS_PONG) { + LLOG(LogInfo) << "[WebSocket] Received PONG: "; + } + else if (meta->flags & CURLWS_PING) { + LLOG(LogInfo) << "[WebSocket] Received PING: "; + } + //else if (meta->flags & CURLWS_TEXT) { + //} } if (res == CURLE_OK) { @@ -349,25 +392,25 @@ namespace l::network { // In this path only if there's an error if (res == CURLE_GOT_NOTHING) { if (mWebSocketCanReceiveData) { - LOG(LogError) << "Failed wss read - 'curl got nothing' - connection closed, error: " << res; + LLOG(LogWarning) << "[Websocket] Failed read - 'curl got nothing' - connection closed, error: " << res; } mWebSocketCanReceiveData = false; } if (res == CURLE_RECV_ERROR) { if (mWebSocketCanReceiveData) { - LOG(LogError) << "Failed wss read - 'curl recieve error' - connection closed, error: " << res; + LLOG(LogWarning) << "[Websocket] Failed read - 'curl recieve error' - connection closed, error: " << res; } mWebSocketCanReceiveData = false; } if (res == CURLE_BAD_FUNCTION_ARGUMENT) { if (mWebSocketCanReceiveData) { - LOG(LogError) << "Failed wss read - 'curl bad function arg', error: " << res; + LLOG(LogWarning) << "[Websocket] Failed read - 'curl bad function arg', error: " << res; } mWebSocketCanReceiveData = false; } if (mWebSocketCanReceiveData) { - LOG(LogError) << "Failed wss read, connection closed, error: " << res; + LLOG(LogWarning) << "[Websocket] Failed read, connection closed, error: " << res; } mWebSocketCanReceiveData = false; SetRunningTimeout(20); @@ -375,16 +418,49 @@ namespace l::network { } void ConnectionBase::WSClose() { + if (HasExpired()) { + mWebSocketCanSendData = false; + LLOG(LogWarning) << "[Websocket] Failed close, connection expired"; + } + if (mCurl == nullptr) { + mWebSocketCanSendData = false; + LLOG(LogWarning) << "[Websocket] Failed close, no curl instance"; + } + + ASSERT(mOngoingRequest); + + if (mCompletedRequest) { + // request probably timed out so discard data + return; + } + size_t sentBytes = 0; auto res = curl_ws_send(mCurl, nullptr, 0, &sentBytes, 0, CURLWS_CLOSE); if (res == CURLE_OK) { - LOG(LogInfo) << "Closed connection"; + LLOG(LogInfo) << "[Websocket] Closed connection"; } NotifyCompleteRequest(true); } + bool ConnectionBase::WSAutoConnectEnabled() { + return mWebSocketAutoConnect; + } + + void ConnectionBase::WSSetAutoConnect(bool autoReconnect) { + mWebSocketAutoConnect = autoReconnect; + } + void ConnectionBase::NotifyAppendHeader(const char* contents, size_t size) { + if (HasExpired()) { + mWebSocketCanSendData = false; + LLOG(LogWarning) << "[Request] Failed notify append header, connection expired"; + } + if (mCurl == nullptr) { + mWebSocketCanSendData = false; + LLOG(LogWarning) << "[Request] Failed notify append header, no curl instance"; + } + ASSERT(mOngoingRequest); if (mCompletedRequest) { @@ -414,7 +490,16 @@ namespace l::network { } void ConnectionBase::NotifyAppendResponse(const char* contents, size_t size) { - ASSERT(mOngoingRequest); + if (HasExpired()) { + mWebSocketCanSendData = false; + LLOG(LogWarning) << "[Request] Failed notify append response, connection expired"; + } + if (mCurl == nullptr) { + mWebSocketCanSendData = false; + LLOG(LogWarning) << "[Request] Failed notify append response, no curl instance"; + } + + //ASSERT(mOngoingRequest); if (mCompletedRequest) { // request probably timed out so discard data diff --git a/packages/network/source/common/NetworkInterfaceWS.cpp b/packages/network/source/common/NetworkInterfaceWS.cpp index 791e5528..d919d597 100644 --- a/packages/network/source/common/NetworkInterfaceWS.cpp +++ b/packages/network/source/common/NetworkInterfaceWS.cpp @@ -30,7 +30,8 @@ namespace l::network { void NetworkInterfaceWS::Disconnect(std::string_view interfaceName) { auto networkManager = mNetworkManager.lock(); if (networkManager) { - networkManager->WSClose(interfaceName); + auto queryName = interfaceName; // With websocket, we have one query only per interface and it has the same name + networkManager->WSClose(queryName); } } @@ -48,7 +49,8 @@ namespace l::network { if (!query.empty()) { auto networkManager = mNetworkManager.lock(); if (networkManager) { - result = networkManager->PostQuery(interfaceName, "", retries, query, expectedResponseSize, timeOut, cb); + auto queryName = interfaceName; // With websocket, we have one query only per interface and it has the same name + result = networkManager->PostQuery(queryName, "", retries, query, expectedResponseSize, timeOut, cb); } } } @@ -63,7 +65,8 @@ namespace l::network { if (NetworkStatus(interfaceName)) { auto networkManager = mNetworkManager.lock(); if (networkManager) { - read = networkManager->WSRead(interfaceName, buffer, size); + auto queryName = interfaceName; // With websocket, we have one query only per interface and it has the same name + read = networkManager->WSRead(queryName, buffer, size); } } } @@ -93,12 +96,13 @@ namespace l::network { auto& queue = it->second.GetQueue(); while (!queue.empty() && maxQueued > 0) { auto& command = queue.front(); - auto written = networkManager->WSWrite(interfaceName, command.c_str(), command.size()); + auto queryName = interfaceName; // With websocket, we have one query only per interface and it has the same name + auto written = networkManager->WSWrite(queryName, command.c_str(), command.size()); if (written > 0) { queue.pop_front(); } else { - LOG(LogWarning) << "Failed to write to: " << interfaceName << " : error: " << written; + LLOG(LogWarning) << "Failed to write to: " << interfaceName << " : error: " << written; } maxQueued--; } @@ -127,6 +131,24 @@ namespace l::network { } } + int32_t NetworkInterfaceWS::Keepalive(std::string_view interfaceName) { + int32_t written = 0; + auto it = mInterfaces.find(interfaceName.data()); + if (it != mInterfaces.end()) { + if (NetworkStatus(interfaceName)) { + auto networkManager = mNetworkManager.lock(); + if (networkManager) { + auto queryName = interfaceName; // With websocket, we have one query only per interface and it has the same name + written = networkManager->WSKeepalive(queryName) >= 0; + if (written < 0) { + LLOG(LogWarning) << "Failed to send keepalive: " << interfaceName << " : error: " << written; + } + } + } + } + return written; + } + int32_t NetworkInterfaceWS::Write(std::string_view interfaceName, const char* buffer, size_t size) { int32_t written = 0; auto it = mInterfaces.find(interfaceName.data()); @@ -134,9 +156,10 @@ namespace l::network { if (NetworkStatus(interfaceName)) { auto networkManager = mNetworkManager.lock(); if (networkManager) { - written = networkManager->WSWrite(interfaceName, buffer, size) >= 0; + auto queryName = interfaceName; // With websocket, we have one query only per interface and it has the same name + written = networkManager->WSWrite(queryName, buffer, size) >= 0; if (written < 0) { - LOG(LogWarning) << "Failed to write to: " << interfaceName << " : error: " << written; + LLOG(LogWarning) << "Failed to write to: " << interfaceName << " : error: " << written; } } } @@ -149,12 +172,36 @@ namespace l::network { if (it != mInterfaces.end()) { auto networkManager = mNetworkManager.lock(); if (networkManager) { - return networkManager->WSConnected(interfaceName); + auto queryName = interfaceName; // With websocket, we have one query only per interface and it has the same name + return networkManager->WSConnected(queryName); } } return false; } + bool NetworkInterfaceWS::IsAutoConnecting(std::string_view interfaceName) { + auto it = mInterfaces.find(interfaceName.data()); + if (it != mInterfaces.end()) { + auto networkManager = mNetworkManager.lock(); + if (networkManager) { + auto queryName = interfaceName; // With websocket, we have one query only per interface and it has the same name + return networkManager->WSAutoConnectEnabled(queryName); + } + } + return false; + } + + void NetworkInterfaceWS::SetAutoConnect(std::string_view interfaceName, bool autoConnect) { + auto it = mInterfaces.find(interfaceName.data()); + if (it != mInterfaces.end()) { + auto networkManager = mNetworkManager.lock(); + if (networkManager) { + auto queryName = interfaceName; // With websocket, we have one query only per interface and it has the same name + networkManager->WSSetAutoConnect(queryName, autoConnect); + } + } + } + bool NetworkInterfaceWS::NetworkStatus(std::string_view interfaceName) { auto it = mInterfaces.find(interfaceName.data()); if (it != mInterfaces.end()) { diff --git a/packages/network/source/common/NetworkManager.cpp b/packages/network/source/common/NetworkManager.cpp index 5542aa97..0b2a9f23 100644 --- a/packages/network/source/common/NetworkManager.cpp +++ b/packages/network/source/common/NetworkManager.cpp @@ -36,30 +36,30 @@ namespace l::network { if (m->data.result != CURLE_OK) { success = false; } - bool foundHandle = false; + //bool foundHandle = false; for (auto& it : mConnections) { if (it->IsHandle(e)) { - foundHandle = true; + //foundHandle = true; if (!it->IsWebSocket()) { it->NotifyCompleteRequest(success); } } } - ASSERT(foundHandle); + //ASSERT(foundHandle); } else if (m) { - LOG(LogWarning) << "Not done"; + LLOG(LogWarning) << "Not done"; } } while (m != nullptr && messagesInQueue > 0); int numfds; mc = curl_multi_poll(mMultiHandle, NULL, 0, 1000, &numfds); if (mc != CURLM_OK) { - LOG(LogError) << "curl_multi_poll failed, code " << mc; + LLOG(LogError) << "curl_multi_poll failed, code " << mc; } } else { - LOG(LogError) << "curl_multi_perform failed, code " << mc; + LLOG(LogError) << "curl_multi_perform failed, code " << mc; } } while (runningHandles > 0 || mJobManager.get() != nullptr || !mConnections.empty()); }); @@ -202,6 +202,25 @@ namespace l::network { } } + int32_t NetworkManager::WSKeepalive(std::string_view queryName) { + std::unique_lock lock(mConnectionsMutex); + auto it = std::find_if(mConnections.begin(), mConnections.end(), [&](std::unique_ptr& request) { + if (queryName == request->GetRequestName()) { + return true; + } + return false; + }); + + if (it == mConnections.end()) { + LLOG(LogError) << "Failed to find connection: " << queryName; + return -201; + } + auto request = it->get(); + lock.unlock(); + + return request->WSKeepalive(); + } + int32_t NetworkManager::WSWrite(std::string_view queryName, const char* buffer, size_t size) { std::unique_lock lock(mConnectionsMutex); auto it = std::find_if(mConnections.begin(), mConnections.end(), [&](std::unique_ptr& request) { @@ -212,7 +231,7 @@ namespace l::network { }); if (it == mConnections.end()) { - LOG(LogError) << "Failed to find connection: " << queryName; + LLOG(LogError) << "Failed to find connection: " << queryName; return -201; } auto request = it->get(); @@ -231,7 +250,7 @@ namespace l::network { }); if (it == mConnections.end()) { - LOG(LogError) << "Failed to find connection: " << queryName; + LLOG(LogError) << "Failed to find connection: " << queryName; return -201; } auto request = it->get(); @@ -257,4 +276,41 @@ namespace l::network { return request->IsAlive(); } + + bool NetworkManager::WSAutoConnectEnabled(std::string_view queryName) { + std::unique_lock lock(mConnectionsMutex); + auto it = std::find_if(mConnections.begin(), mConnections.end(), [&](std::unique_ptr& request) { + if (queryName == request->GetRequestName()) { + return true; + } + return false; + }); + + if (it == mConnections.end()) { + return false; + } + auto request = it->get(); + lock.unlock(); + + return request->WSAutoConnectEnabled(); + } + + void NetworkManager::WSSetAutoConnect(std::string_view queryName, bool autoConnect) { + std::unique_lock lock(mConnectionsMutex); + auto it = std::find_if(mConnections.begin(), mConnections.end(), [&](std::unique_ptr& request) { + if (queryName == request->GetRequestName()) { + return true; + } + return false; + }); + + if (it == mConnections.end()) { + return ; + } + auto request = it->get(); + lock.unlock(); + + request->WSSetAutoConnect(autoConnect); + } + } diff --git a/packages/network/tests/common/NetworkInterfaceTest.cpp b/packages/network/tests/common/NetworkInterfaceTest.cpp index f3ea0961..7165c710 100644 --- a/packages/network/tests/common/NetworkInterfaceTest.cpp +++ b/packages/network/tests/common/NetworkInterfaceTest.cpp @@ -39,8 +39,8 @@ TEST(NetworkInterface, Setup) { failed = !success; - LOG(LogInfo) << "Query arguments: '" << queryArguments << "'"; - LOG(LogInfo) << request.GetResponse().str(); + LLOG(LogInfo) << "Query arguments: '" << queryArguments << "'"; + LLOG(LogInfo) << request.GetResponse().str(); return success ? l::concurrency::RunnableResult::SUCCESS : l::concurrency::RunnableResult::FAILURE; }; diff --git a/packages/network/tests/common/NetworkManagerTest.cpp b/packages/network/tests/common/NetworkManagerTest.cpp index 328fd23e..d2e89671 100644 --- a/packages/network/tests/common/NetworkManagerTest.cpp +++ b/packages/network/tests/common/NetworkManagerTest.cpp @@ -20,7 +20,7 @@ TEST(NetworkManager, Setup) { [&](bool success, std::string_view queryArguments, l::network::RequestStringStream& request) { TEST_TRUE_NO_RET(success, ""); - LOG(LogInfo) << "Query arguments: '" << queryArguments << "'"; + LLOG(LogInfo) << "Query arguments: '" << queryArguments << "'"; file.modeWriteTrunc(); if (file.open()) { file.write(request.GetResponse()); @@ -34,8 +34,8 @@ TEST(NetworkManager, Setup) { [&](bool success, std::string_view queryArguments, l::network::RequestStringStream& request) { TEST_TRUE_NO_RET(success, ""); - LOG(LogInfo) << "Query arguments: '" << queryArguments << "'"; - LOG(LogInfo) << request.GetResponse().str(); + LLOG(LogInfo) << "Query arguments: '" << queryArguments << "'"; + LLOG(LogInfo) << request.GetResponse().str(); return l::concurrency::RunnableResult::SUCCESS; } ); @@ -66,8 +66,8 @@ TEST(NetworkManager, Setup) { [&](bool success, std::string_view queryArguments, l::network::RequestStringStream& request) { TEST_TRUE_NO_RET(success, ""); - LOG(LogInfo) << "Query arguments: '" << queryArguments << "'"; - LOG(LogInfo) << request.GetResponse().str(); + LLOG(LogInfo) << "Query arguments: '" << queryArguments << "'"; + LLOG(LogInfo) << request.GetResponse().str(); return l::concurrency::RunnableResult::SUCCESS; } ); diff --git a/packages/network/tests/common/NetworkWebSocketTest.cpp b/packages/network/tests/common/NetworkWebSocketTest.cpp index 104c235e..2f02c1dc 100644 --- a/packages/network/tests/common/NetworkWebSocketTest.cpp +++ b/packages/network/tests/common/NetworkWebSocketTest.cpp @@ -43,7 +43,7 @@ TEST(NetworkWebSocket, Setup) { TEST_TRUE(read > 0, ""); - LOG(LogInfo) << std::string_view(buffer, read); + LLOG(LogInfo) << std::string_view(buffer, read); networkInterfaceWS->Disconnect("Websocket"); @@ -69,7 +69,7 @@ TEST(NetworkWebSocket, BinanceKlines) { std::string_view, l::network::WebSocket& ws) { failed = !success; - LOG(LogInfo) << "Success: " << success; + LLOG(LogInfo) << "Success: " << success; return success ? l::concurrency::RunnableResult::SUCCESS : l::concurrency::RunnableResult::FAILURE; }; @@ -104,7 +104,7 @@ TEST(NetworkWebSocket, BinanceKlines) { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); read = networkInterfaceWS->Read("Binance", &buffer[0], 1024); if (read > 0) { - LOG(LogInfo) << std::string_view(buffer, read); + LLOG(LogInfo) << std::string_view(buffer, read); } } while (readCount-- >= 0); @@ -135,7 +135,7 @@ TEST(NetworkWebSocket, BinanceUIKlines) { std::string_view queryArguments, l::network::WebSocket& ws) { failed = !success; - LOG(LogInfo) << "Success: " << success; + LLOG(LogInfo) << "Success: " << success; return success ? l::concurrency::RunnableResult::SUCCESS : l::concurrency::RunnableResult::FAILURE; }; @@ -172,7 +172,7 @@ TEST(NetworkWebSocket, BinanceUIKlines) { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); read = networkInterfaceWS->Read("Binance", &buffer[0], 1024); if (read > 0) { - LOG(LogInfo) << std::string_view(buffer, read); + LLOG(LogInfo) << std::string_view(buffer, read); } } while (readCount-- >= 0); @@ -184,7 +184,7 @@ TEST(NetworkWebSocket, BinanceUIKlines) { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); read = networkInterfaceWS->Read("Binance", &buffer[0], 1024); if (read > 0) { - LOG(LogInfo) << std::string_view(buffer, read); + LLOG(LogInfo) << std::string_view(buffer, read); } } while (readCount-- >= 0); diff --git a/packages/nn/CMakeLists.txt b/packages/nn/CMakeLists.txt new file mode 100644 index 00000000..95d93e53 --- /dev/null +++ b/packages/nn/CMakeLists.txt @@ -0,0 +1,62 @@ +project (nn) + +set(deps + logging + testing + memory + serialization + + filesystem +) + +set(deps_external + floatfann_static + ncnn + tiny_dnn_static +) + +if(TARGET libtorch_wrapper) + list(APPEND deps_external + libtorch_wrapper + ) +endif() + +bs_generate_package(nn "tier1" "${deps}" "${deps_external}") +set_target_properties(nn PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES) +set_target_properties(nn_test PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES) + +if(TARGET libtorch_wrapper) + message("libtorch: ${LDEPS_LIBTORCH_LIB_DIR}") + message("cuda: ${CUDA_TOOLKIT_ROOT_DIR}") + + target_compile_definitions(nn PUBLIC HAS_LIBTORCH=1) + target_compile_definitions(nn_test PUBLIC HAS_LIBTORCH=1) + + if(WIN32) + bs_copy_shared_lib(nn_test "${LDEPS_LIBTORCH_LIB_DIR}/c10") + bs_copy_shared_lib(nn_test "${LDEPS_LIBTORCH_LIB_DIR}/torch") + bs_copy_shared_lib(nn_test "${LDEPS_LIBTORCH_LIB_DIR}/torch_cpu") + if(USE_LIBTORCH_CUDA) + bs_copy_shared_lib(nn_test "${LDEPS_LIBTORCH_LIB_DIR}/c10_cuda") + bs_copy_shared_lib(nn_test "${LDEPS_LIBTORCH_LIB_DIR}/torch_cuda") + bs_copy_shared_lib(nn_test "${LDEPS_LIBTORCH_LIB_DIR}/libiomp5md") + bs_copy_shared_lib(nn_test "${LDEPS_LIBTORCH_LIB_DIR}/cupti64_2025.3.0") + endif() + else() + bs_copy_shared_lib(nn_test "${LDEPS_LIBTORCH_LIB_DIR}/libc10") + bs_copy_shared_lib(nn_test "${LDEPS_LIBTORCH_LIB_DIR}/libtorch") + bs_copy_shared_lib(nn_test "${LDEPS_LIBTORCH_LIB_DIR}/libtorch_cpu") + if(USE_LIBTORCH_CUDA) + bs_copy_shared_lib(nn_test "${LDEPS_LIBTORCH_LIB_DIR}/libc10_cuda") + bs_copy_shared_lib(nn_test "${LDEPS_LIBTORCH_LIB_DIR}/libtorch_cuda") + endif() + endif() +endif() + +if(MSVC) + target_compile_options(nn PRIVATE /WX- /bigobj) + target_compile_options(nn_test PRIVATE /WX- /bigobj) +else() + target_compile_options(nn PRIVATE -Wno-unused-variable -Wno-error -Wno-unused-variable -Wno-unused-result -Wno-ignored-qualifiers -Wno-unused-parameter) + target_compile_options(nn_test PRIVATE -Wno-unused-variable -Wno-error -Wno-unused-variable -Wno-unused-result -Wno-ignored-qualifiers -Wno-unused-parameter) +endif() diff --git a/packages/nn/include/nn/NNBase.h b/packages/nn/include/nn/NNBase.h new file mode 100644 index 00000000..eeb34543 --- /dev/null +++ b/packages/nn/include/nn/NNBase.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include +#include +#include + +namespace l::nn { + + class NNBase { + public: + NNBase() = default; + NNBase(std::string name) : name_(std::move(name)) {} + virtual ~NNBase() = default; + + // Perform inference given input vector; returns optional output + virtual std::optional infer(const InputVec& input) = 0; + + // Train if this time range has not been trained yet + bool trainIfNeeded(const TrainingExample& example) { + if (!hasTrainedOn(example.time)) { + forceTrain(example); + markTrained(example.time); + return true; + } + return false; + } + + // Force training, regardless of training history + virtual void forceTrain(const TrainingExample& example) = 0; + + virtual void loadModel(const std::string& path) = 0; + virtual void saveModel(const std::string& path) const = 0; + + bool hasTrainedOn(const TimeRange& range) const { + for (const auto& trained : trained_ranges_) { + if (trained.overlaps(range)) return true; + } + return false; + } + + void markTrained(const TimeRange& range) { + trained_ranges_.push_back(range); + // Optional: merge overlapping ranges to keep list small (not implemented here) + } + + const std::string& name() const { return name_; } + + protected: + std::string name_; + std::vector trained_ranges_; + }; + +} \ No newline at end of file diff --git a/packages/nn/include/nn/NNUtils.h b/packages/nn/include/nn/NNUtils.h new file mode 100644 index 00000000..f9ec3957 --- /dev/null +++ b/packages/nn/include/nn/NNUtils.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace l::nn { + + using UnixTime = int32_t; // Unix timestamp in seconds + + struct TimeRange { + UnixTime start; // inclusive + UnixTime end; // exclusive + + bool overlaps(const TimeRange& other) const { + return start < other.end && end > other.start; + } + + bool contains(UnixTime t) const { + return t >= start && t < end; + } + + int32_t duration() const { return end - start; } + }; + + // ---------- Data Types ---------- + + using InputVec = std::vector; + using TargetVec = std::vector; + + struct TrainingExample { + TimeRange time; + InputVec input; + TargetVec target; + }; + +} \ No newline at end of file diff --git a/packages/nn/include/nn/SlidingWindowBuffer.h b/packages/nn/include/nn/SlidingWindowBuffer.h new file mode 100644 index 00000000..0f8d1393 --- /dev/null +++ b/packages/nn/include/nn/SlidingWindowBuffer.h @@ -0,0 +1,70 @@ +#pragma once + +#include + +#include +#include +#include + +namespace l::nn { + + /** + * Collects timestamped inputs and allows querying a fixed-size window + */ + class SlidingWindowBuffer { + public: + SlidingWindowBuffer(size_t window_size_seconds, size_t sample_rate_hz) + : window_size_(window_size_seconds), sample_rate_(sample_rate_hz) + { + max_samples_ = window_size_ * sample_rate_; + } + + // Add a new timestamped sample (assumes monotonically increasing time) + void push(UnixTime timestamp, const InputVec& input) { + assert(!input.empty()); + timestamps_.push_back(timestamp); + inputs_.push_back(input); + + // Maintain max size + while (!timestamps_.empty() && timestamps_.front() < timestamp - window_size_) { + timestamps_.erase(timestamps_.begin()); + inputs_.erase(inputs_.begin()); + } + } + + // Return concatenated input vector for current window if complete + std::optional getWindow() const { + if (timestamps_.empty()) return std::nullopt; + + UnixTime window_start = timestamps_.front(); + UnixTime window_end = timestamps_.back(); + + // Ensure window covers the full requested duration + if (window_end - window_start + 1 < static_cast(window_size_)) { + return std::nullopt; // Not enough data yet + } + + // Concatenate all input vectors into one big vector + InputVec concatenated; + for (const auto& sample : inputs_) { + concatenated.insert(concatenated.end(), sample.begin(), sample.end()); + } + return concatenated; + } + + TimeRange currentTimeRange() const { + if (timestamps_.empty()) return { 0,0 }; + return { timestamps_.front(), timestamps_.back() + 1 }; // +1 exclusive + } + + private: + size_t window_size_; // seconds + size_t sample_rate_; // samples per second + size_t max_samples_; + + std::vector timestamps_; + std::vector inputs_; + }; + +} + diff --git a/packages/nn/include/nn/fann/NNFannBase.h b/packages/nn/include/nn/fann/NNFannBase.h new file mode 100644 index 00000000..80a76706 --- /dev/null +++ b/packages/nn/include/nn/fann/NNFannBase.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define FANN_NO_DLL +#include + +namespace l::nn::fann { + + class NNFannBase : public NNBase { + public: + NNFannBase() = default; + NNFannBase(std::string name, unsigned inputSize, unsigned outputSize) + : NNBase(std::move(name)), input_size_(inputSize), output_size_(outputSize) + { + + net_ = fann_create_standard(3, inputSize, 16, outputSize); + fann_set_training_algorithm(net_, FANN_TRAIN_RPROP); + fann_set_learning_rate(net_, 0.01f); + } + + ~NNFannBase() override = default; + + std::optional infer(const InputVec& input) override; + void forceTrain(const TrainingExample& ex) override; + void loadModel(const std::string& path) override; + void saveModel(const std::string& path) const override; + + private: + struct fann* net_ = nullptr; + unsigned input_size_; + unsigned output_size_; + }; + + +} \ No newline at end of file diff --git a/packages/nn/include/nn/tinydnn/TrendReversalPredictor.h b/packages/nn/include/nn/tinydnn/TrendReversalPredictor.h new file mode 100644 index 00000000..fbe8b321 --- /dev/null +++ b/packages/nn/include/nn/tinydnn/TrendReversalPredictor.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include +#include + +namespace l::nn::tinydnn { + + struct Sample { + tiny_dnn::vec_t ts_data; // OHLC: seq_len * 4 + tiny_dnn::vec_t ext_data; // Support/resistance: 4 + tiny_dnn::label_t label; // 0 or 1 + }; + + class TrendReversalPredictor { + private: + size_t seq_len; + size_t ts_features; + size_t kernel_size, stride, conv_filters; + size_t conv_out_height, conv_out_width; + size_t fc_in_size; + tiny_dnn::vec_t ts_buffer; + tiny_dnn::network net; + + public: + TrendReversalPredictor(size_t seq_len_, size_t ts_features_); + void train(std::vector& dataset, int epochs = 50, float lr = 0.001); + void update_context(const tiny_dnn::vec_t& new_ts_data, float price_min, float price_max); + float predict(); + void normalize(tiny_dnn::vec_t& data, float min, float max); + static std::vector load_dataset(const std::string& file_path, size_t seq_len, size_t ts_features); + }; +} diff --git a/packages/nn/include/nn/tinydnn/flatten_layer.h b/packages/nn/include/nn/tinydnn/flatten_layer.h new file mode 100644 index 00000000..6d5cc197 --- /dev/null +++ b/packages/nn/include/nn/tinydnn/flatten_layer.h @@ -0,0 +1,70 @@ +#pragma once + +#include + +#include + +namespace l::nn::tinydnn { + + using namespace tiny_dnn; + + class flatten_layer : public layer { + public: + flatten_layer(size_t in_height, size_t in_width, size_t in_channels = 1) + : layer({ vector_type::data }, { vector_type::data }), + in_height_(in_height), in_width_(in_width), in_channels_(in_channels), + out_size_(in_height* in_width* in_channels) { + } + + std::string layer_type() const override { return "flatten"; } + + void forward_propagation(const std::vector& in_data, + std::vector& out_data) override { + const tensor_t& input = *(in_data[0]); + tensor_t& output = *(out_data[0]); + for (size_t i = 0; i < input.size(); ++i) { + output[i].resize(out_size_); + size_t idx = 0; + for (size_t c = 0; c < in_channels_; ++c) { + for (size_t h = 0; h < in_height_; ++h) { + for (size_t w = 0; w < in_width_; ++w) { + output[i][idx++] = input[i][c * in_height_ * in_width_ + h * in_width_ + w]; + } + } + } + } + } + + void back_propagation(const std::vector& in_data, + const std::vector& out_data, + std::vector& out_grad, + std::vector& in_grad) override { + const tensor_t& out_gradient = *(out_grad[0]); + tensor_t& in_gradient = *(in_grad[0]); + for (size_t i = 0; i < out_gradient.size(); ++i) { + in_gradient[i].resize(out_size_); + size_t idx = 0; + for (size_t c = 0; c < in_channels_; ++c) { + for (size_t h = 0; h < in_height_; ++h) { + for (size_t w = 0; w < in_width_; ++w) { + in_gradient[i][c * in_height_ * in_width_ + h * in_width_ + w] = + out_gradient[i][idx++]; + } + } + } + } + } + + std::vector> in_shape() const override { + return { {in_height_, in_width_, in_channels_} }; + } + + std::vector> out_shape() const override { + return { {out_size_, 1, 1} }; + } + + private: + size_t in_height_, in_width_, in_channels_, out_size_; + }; + +} // \ No newline at end of file diff --git a/packages/nn/include/nn/torch/TorchBase.h b/packages/nn/include/nn/torch/TorchBase.h new file mode 100644 index 00000000..48a91dc5 --- /dev/null +++ b/packages/nn/include/nn/torch/TorchBase.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +#ifdef HAS_LIBTORCH +#include +#endif + +namespace l::nn::libtorch { + class Transformer { + public: + void process(const std::vector& window); + }; + +} + diff --git a/packages/nn/source/common/fann/NNFannBase.cpp b/packages/nn/source/common/fann/NNFannBase.cpp new file mode 100644 index 00000000..fd21ed53 --- /dev/null +++ b/packages/nn/source/common/fann/NNFannBase.cpp @@ -0,0 +1,36 @@ +#include + +namespace l::nn::fann { + + std::optional NNFannBase::infer(const InputVec& input) { + if (input.size() != input_size_) return std::nullopt; + + float* output = fann_run(net_, const_cast(input.data())); + return TargetVec(output, output + output_size_); + } + + void NNFannBase::forceTrain(const TrainingExample& ex) { + if (ex.input.size() != input_size_ || ex.target.size() != output_size_) { + std::cerr << "Input/target size mismatch in forceTrain\n"; + return; + } + fann_train(net_, const_cast(ex.input.data()), const_cast(ex.target.data())); + // markTrained called by trainIfNeeded + } + + void NNFannBase::loadModel(const std::string& path) { + struct fann* loaded = fann_create_from_file(path.c_str()); + if (loaded) { + fann_destroy(net_); + net_ = nullptr; + } + else { + std::cerr << "Failed to load FANN model from " << path << "\n"; + } + } + + void NNFannBase::saveModel(const std::string& path) const { + fann_save(net_, path.c_str()); + } + +} diff --git a/packages/nn/source/common/tinydnn/TrendReversalPredictor.cpp b/packages/nn/source/common/tinydnn/TrendReversalPredictor.cpp new file mode 100644 index 00000000..9ae225b5 --- /dev/null +++ b/packages/nn/source/common/tinydnn/TrendReversalPredictor.cpp @@ -0,0 +1,141 @@ +#include + +#include + +#include +#include +#include +#include + +namespace l::nn::tinydnn { + + + // Binary Cross Entropy Loss + class bce_loss { + public: + static float_t f(const vec_t& y, const vec_t& t) { + if (y.size() != 1 || t.size() != 1) throw std::runtime_error("BCE requires scalar output."); + float_t y_val = y[0], t_val = t[0]; + return -t_val * log(y_val + 1e-10f) - (1.0f - t_val) * log(1.0f - y_val + 1e-10f); + } + + static vec_t df(const vec_t& y, const vec_t& t) { + if (y.size() != 1 || t.size() != 1) throw std::runtime_error("BCE requires scalar output."); + float_t y_val = y[0], t_val = t[0]; + float_t grad = (y_val - t_val) / ((y_val + 1e-10f) * (1.0f - y_val + 1e-10f)); + return { grad }; + } + }; + + TrendReversalPredictor::TrendReversalPredictor(size_t seq_len_, size_t ts_features_) + : seq_len(seq_len_), ts_features(ts_features_), + ts_buffer(seq_len* ts_features, 0.0f) { + using namespace tiny_dnn; + // Layer config parameters + kernel_size = 3; + stride = 1; + conv_filters = 8; + + conv_out_height = (seq_len - kernel_size) / stride + 1; // e.g. 28 + conv_out_width = conv_filters; // e.g. 8 + + fc_in_size = conv_out_height * conv_out_width * 2; // flatten output size, no ext_features + + net << convolutional_layer(seq_len, ts_features, kernel_size, 1, conv_filters, padding::valid) + << relu_layer() + << flatten_layer(conv_out_height, 2, conv_out_width) + << fully_connected_layer(fc_in_size, 1) + << sigmoid_layer(); + } + + void TrendReversalPredictor::normalize(vec_t& data, float min, float max) { + if (max == min) return; + for (auto& val : data) val = (val - min) / (max - min); + } + + void TrendReversalPredictor::train(std::vector& dataset, int epochs, float lr) { + adam optimizer; + optimizer.alpha = lr; + const int batch_size = 8; + + std::random_device rd; + std::mt19937 g(rd()); + + for (int epoch = 0; epoch < epochs; ++epoch) { + std::shuffle(dataset.begin(), dataset.end(), g); + float total_loss = 0.0f; + size_t batch_count = 0; + + for (size_t i = 0; i < dataset.size(); i += batch_size) { + size_t batch_end = std::min(i + batch_size, dataset.size()); + std::vector batch_data; + std::vector batch_labels; + + for (size_t j = i; j < batch_end; ++j) { + vec_t input = dataset[j].ts_data; + input.insert(input.end(), dataset[j].ext_data.begin(), dataset[j].ext_data.end()); + batch_data.push_back(input); + batch_labels.push_back({ static_cast(dataset[j].label) }); + } + + bool success = net.train(optimizer, batch_data, batch_labels, batch_size, 1); + if (!success) { + std::cerr << "Training failed for batch " << batch_count + 1 << "\n"; + continue; + } + + float batch_loss = 0.0f; + for (size_t j = 0; j < batch_data.size(); ++j) { + vec_t output = net.predict(batch_data[j]); + batch_loss += bce_loss::f(output, batch_labels[j]); + } + batch_loss /= batch_data.size(); + total_loss += batch_loss; + ++batch_count; + } + + std::cout << "Epoch " << epoch + 1 << ", Loss: " << total_loss / batch_count << "\n"; + } + + net.save("trend_model.bin"); + } + + void TrendReversalPredictor::update_context(const vec_t& new_ts_data, float price_min, float price_max) { + if (new_ts_data.size() == ts_features) { + std::copy(ts_buffer.begin() + ts_features, ts_buffer.end(), ts_buffer.begin()); + auto normalized_ts = new_ts_data; + normalize(normalized_ts, price_min, price_max); + std::copy(normalized_ts.begin(), normalized_ts.end(), ts_buffer.end() - ts_features); + } + } + + float TrendReversalPredictor::predict() { + using namespace tiny_dnn; + return net.predict(ts_buffer)[0]; + } + + std::vector TrendReversalPredictor::load_dataset(const std::string& file_path, size_t seq_len, size_t ts_features) { + std::vector dataset; + std::ifstream file(file_path); + std::string line; + while (std::getline(file, line)) { + std::stringstream ss(line); + vec_t ts_data(seq_len * ts_features); + vec_t ext_data(4); + label_t label; + for (size_t i = 0; i < seq_len * ts_features; ++i) { + ss >> ts_data[i]; + if (ss.peek() == ',') ss.ignore(); + } + for (size_t i = 0; i < 4; ++i) { + ss >> ext_data[i]; + if (ss.peek() == ',') ss.ignore(); + } + ss >> label; + dataset.push_back({ ts_data, ext_data, label }); + } + return dataset; + } + + +} diff --git a/packages/nn/source/common/tinydnn/flatten_layer.cpp b/packages/nn/source/common/tinydnn/flatten_layer.cpp new file mode 100644 index 00000000..7247ccf1 --- /dev/null +++ b/packages/nn/source/common/tinydnn/flatten_layer.cpp @@ -0,0 +1 @@ +#include diff --git a/packages/nn/source/common/torch/TorchBase.cpp b/packages/nn/source/common/torch/TorchBase.cpp new file mode 100644 index 00000000..6ea5829f --- /dev/null +++ b/packages/nn/source/common/torch/TorchBase.cpp @@ -0,0 +1,44 @@ +#include + +namespace l::nn::libtorch { + +#ifdef HAS_LIBTORCH + struct TransformerNetImpl : public torch::nn::Module { + TransformerNetImpl(int window_size, int output_size) { + auto copt = torch::nn::Conv1dOptions(1, 16, 5).stride(1).padding(2); + conv = register_module("conv", torch::nn::Conv1d(copt)); + transformer = register_module("transformer", torch::nn::Transformer(torch::nn::TransformerOptions(16, 8).d_model(16).nhead(8))); + fc = register_module("fc", torch::nn::Linear(16 * window_size, output_size)); + } + torch::Tensor forward(torch::Tensor x) { + x = x.unsqueeze(1); + x = torch::relu(conv->forward(x)); + x = transformer->forward(x, x); + x = x.view({ x.size(0), -1 }); + return fc->forward(x); + } + + // Define transformer as in previous messages + torch::nn::Conv1d conv = nullptr; + torch::nn::Transformer transformer = nullptr; + torch::nn::Linear fc = nullptr; + }; + TORCH_MODULE(TransformerNet); +#endif + + void Transformer::process(const std::vector& window) { +#ifdef HAS_LIBTORCH + TransformerNet model(window.size(), 10); // Predict 10 samples + model->eval(); + torch::NoGradGuard no_grad; + auto input = torch::tensor(window).reshape({ 1, -1 }); + auto output = model->forward(input); + std::vector result(output.data_ptr(), output.data_ptr() + 10); + + std::cout << "result[0]: " << result.at(0); +#else + // Fallback: Simple moving average or skip + std::cout << "Torch not available. Skipping transformer processing.\n"; +#endif + } +} diff --git a/packages/nn/tests/common/FannTest.cpp b/packages/nn/tests/common/FannTest.cpp new file mode 100644 index 00000000..359066a3 --- /dev/null +++ b/packages/nn/tests/common/FannTest.cpp @@ -0,0 +1,57 @@ +#include "testing/Test.h" +#include "logging/Log.h" + +#include +#include + +using namespace l::nn; +using namespace l::nn::fann; + + +TEST(Fann, SlidingWindow) { + + // Setup sliding window for 10 seconds at 1 sample per second + SlidingWindowBuffer sliding_window(10, 1); + + // Create a node that expects input size = 4 (10 seconds * 1 sample/sec * 4 features) + // Actually, since sliding_window concatenates all inputs in window, if each input is 1 feature: + // input_size = window_size * sample_rate * features_per_sample + const unsigned features_per_sample = 1; + const unsigned input_size = 10 * 1 * features_per_sample; + const unsigned output_size = 1; + + NNFannBase node("example_node", input_size, output_size); + + // Feed data into the sliding window over time + for (UnixTime t = 1000; t < 1020; ++t) { + InputVec sample = { static_cast(t % 10) }; // dummy input feature + sliding_window.push(t, sample); + + // Once we have a full window, prepare training example + auto window_input = sliding_window.getWindow(); + if (window_input) { + TrainingExample ex; + ex.time = sliding_window.currentTimeRange(); + ex.input = *window_input; + // Dummy target for demo + ex.target = {0.5f}; + + if (node.trainIfNeeded(ex)) { + std::cout << "Trained on time range [" << ex.time.start << ", " << ex.time.end << ")\n"; + } + else { + std::cout << "Already trained on this time range\n"; + } + + // Try inference + auto pred = node.infer(ex.input); + if (pred) { + std::cout << "Inference output: " << (*pred)[0] << "\n"; + } + } + } + + return 0; +} + + diff --git a/packages/nn/tests/common/NNTest.cpp b/packages/nn/tests/common/NNTest.cpp new file mode 100644 index 00000000..6d728e9e --- /dev/null +++ b/packages/nn/tests/common/NNTest.cpp @@ -0,0 +1,7 @@ +#include "testing/Test.h" + +int main(int, char* argw[]) { + TEST_RUN(argw[0]); + + return 0; +} diff --git a/packages/nn/tests/common/TinyDnnTest.cpp b/packages/nn/tests/common/TinyDnnTest.cpp new file mode 100644 index 00000000..743bb4ae --- /dev/null +++ b/packages/nn/tests/common/TinyDnnTest.cpp @@ -0,0 +1,36 @@ +#include "testing/Test.h" +#include "logging/Log.h" + +#include + +#include + +using namespace l::nn::tinydnn; + +TEST(TinyDnn, Basic) { + + return 0 ; + + size_t seq_len = 30; + size_t ts_features = 4; + + TrendReversalPredictor predictor(seq_len, ts_features); + auto dataset = TrendReversalPredictor::load_dataset("dataset/data.csv", seq_len, ts_features); + + float price_min = 90.0f, price_max = 110.0f; + for (auto& sample : dataset) { + predictor.normalize(sample.ts_data, price_min, price_max); + } + + predictor.train(dataset, 50, 0.001); + + tiny_dnn::vec_t new_ts_data = { 100.5f, 101.0f, 100.0f, 100.8f }; + predictor.update_context(new_ts_data, price_min, price_max); + float prob = predictor.predict(); + std::cout << "Trend reversal probability: " << prob * 100 << "%\n"; + + return 0; + +} + + diff --git a/packages/nn/tests/common/Torch1Test.cpp b/packages/nn/tests/common/Torch1Test.cpp new file mode 100644 index 00000000..f8105f16 --- /dev/null +++ b/packages/nn/tests/common/Torch1Test.cpp @@ -0,0 +1,32 @@ +#include "testing/Test.h" +#include "logging/Log.h" + +#ifdef HAS_LIBTORCH +#include + +#include +#include +#include +#include +#include + +TEST(Torch1, Basic) { + return 0; + l::nn::libtorch::Transformer tf; + std::vector input; + + std::random_device rdev; + + srand(45924296592); + float acc = 1.0f; + for (int32_t i = 0; i < 100; i++) { + input.push_back(acc); + acc *= 1.0f + 0.1f * (rand() / static_cast(RAND_MAX) - 0.5f); + } + std::vector output; + tf.process(input); + + return 0; +} +#endif + diff --git a/packages/nn/tests/common/Torch2Test.cpp b/packages/nn/tests/common/Torch2Test.cpp new file mode 100644 index 00000000..88c4f75a --- /dev/null +++ b/packages/nn/tests/common/Torch2Test.cpp @@ -0,0 +1,118 @@ +#include "testing/Test.h" +#include "logging/Log.h" + +#ifdef HAS_LIBTORCH +#include + +TEST(Torch2, TorchTensorBasic) { + torch::Tensor tensor = torch::eye(3); + std::cout << tensor << std::endl; + return 0; +} + +struct Net1Impl : torch::nn::Module { + Net1Impl(int64_t N, int64_t M) { + linear = register_module("linear", torch::nn::Linear(N, M)); + another_bias = register_parameter("b", torch::randn(M)); + } + torch::Tensor forward(torch::Tensor input) { + return linear(input) + another_bias; + } + torch::nn::Linear linear = nullptr; + torch::Tensor another_bias; +}; +TORCH_MODULE(Net1); + +TEST(Torch2, TorchNetBasic) { + return 0; + Net1 net(4, 5); + + for (const auto& p : net->parameters()) { + std::cout << p << std::endl; + } + return 0; +} + + +// Define a new Module. +struct Net2Impl : torch::nn::Module { + Net2Impl() { + // Construct and register two Linear submodules. + fc1 = register_module("fc1", torch::nn::Linear(784, 64)); + fc2 = register_module("fc2", torch::nn::Linear(64, 32)); + fc3 = register_module("fc3", torch::nn::Linear(32, 10)); + //fc1->to(torch::kCUDA); + //fc2->to(torch::kCUDA); + //fc3->to(torch::kCUDA); + } + + TORCH_API torch::serialize::OutputArchive& operator<<( + torch::serialize::OutputArchive& archive) { + archive << fc1; + archive << fc2; + archive << fc3; + } + + /// Deserializes a `Module` from an `InputArchive`. + TORCH_API torch::serialize::InputArchive& operator>>( + torch::serialize::InputArchive& archive) { + archive >> fc3; + archive >> fc2; + archive >> fc1; + } + + // Implement the Net's algorithm. + torch::Tensor forward(torch::Tensor x) { + // Use one of many tensor manipulation functions. + x = torch::relu(fc1->forward(x.reshape({ x.size(0), 784 }))); + x = torch::dropout(x, /*p=*/0.5, /*train=*/is_training()); + x = torch::relu(fc2->forward(x)); + x = torch::log_softmax(fc3->forward(x), /*dim=*/1); + return x; + } + + // Use one of many "standard library" modules. + torch::nn::Linear fc1{ nullptr }, fc2{ nullptr }, fc3{ nullptr }; +}; +TORCH_MODULE(Net2); + +TEST(Torch2, TorchAdvancedExample) { + return 0; + // Create a new Net. + auto net = std::make_shared(); + + // Create a multi-threaded data loader for the MNIST dataset. + auto data_loader = torch::data::make_data_loader( + torch::data::datasets::MNIST("./data").map( + torch::data::transforms::Stack<>()), + /*batch_size=*/64); + + // Instantiate an SGD optimization algorithm to update our Net's parameters. + torch::optim::SGD optimizer(net->get()->parameters(), /*lr=*/0.01); + + for (size_t epoch = 1; epoch <= 10; ++epoch) { + size_t batch_index = 0; + // Iterate the data loader to yield batches from the dataset. + for (auto& batch : *data_loader) { + // Reset gradients. + optimizer.zero_grad(); + // Execute the model on the input data. + torch::Tensor prediction = net->get()->forward(batch.data); + // Compute a loss value to judge the prediction of our model. + torch::Tensor loss = torch::nll_loss(prediction, batch.target); + // Compute gradients of the loss w.r.t. the parameters of our model. + loss.backward(); + // Update the parameters based on the calculated gradients. + optimizer.step(); + // Output the loss and checkpoint every 100 batches. + if (++batch_index % 100 == 0) { + std::cout << "Epoch: " << epoch << " | Batch: " << batch_index + << " | Loss: " << loss.item() << std::endl; + // Serialize your model periodically as a checkpoint. + //torch::save(net, "net.pt"); + } + } + } + return 0; +} +#endif diff --git a/packages/nn/tests/common/TorchTest.cpp b/packages/nn/tests/common/TorchTest.cpp new file mode 100644 index 00000000..69734d46 --- /dev/null +++ b/packages/nn/tests/common/TorchTest.cpp @@ -0,0 +1,318 @@ +#include "testing/Test.h" +#include "logging/Log.h" + +#ifdef HAS_LIBTORCH +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace torch::indexing; + +// === 1. Multi-Octave Sliding Window Trainer === +class OctaveTrainer : torch::nn::Module { +public: + OctaveTrainer() = default; + ~OctaveTrainer() = default; + + struct Window { + std::deque X, y; + int size; + + Window(int s) : size(s) {} + + void add(torch::Tensor x, torch::Tensor target) { + X.push_back(x); + y.push_back(target); + if ((int)X.size() > size) { + X.pop_front(); + y.pop_front(); + } + } + }; + + std::vector windows; + torch::nn::Linear model{nullptr}; + std::unique_ptr optimizer; + torch::Device device; + + OctaveTrainer(int input_dim) : device(torch::kCPU) { + // Check for CUDA availability + if (torch::cuda::is_available()) { + std::cout << "CUDA is available! Training on GPU." << std::endl; + device = torch::Device(torch::kCUDA); + } + else { + std::cout << "CUDA is not available. Training on CPU." << std::endl; + } + + // Initialize windows correctly + windows.emplace_back(30); + windows.emplace_back(90); + windows.emplace_back(365); + + // Create the model properly using register_module + model = register_module("linear", torch::nn::Linear(input_dim, 2)); + + // Move model to device + model->to(device); + + // Initialize weights and biases after registration + torch::nn::init::normal_(model->weight, 0.0, 0.1); + torch::nn::init::constant_(model->bias, 0.0); + + optimizer = std::make_unique(torch::optim::Adam(model->parameters(), torch::optim::AdamOptions(1e-3))); + } + + void train_step(torch::Tensor x, torch::Tensor target) { + // Move tensors to device + x = x.to(device); + target = target.to(device); + + model->train(); + optimizer->zero_grad(); + auto out = model->forward(x); + auto mean = out.slice(1, 0, 1); + auto logvar = out.slice(1, 1, 2); + auto loss = ((target - mean).square() / logvar.exp() + logvar).mean(); + loss.backward(); + optimizer->step(); + + // Update all windows + for (auto& w : windows) { + w.add(x.detach(), target.detach()); + } + } + + torch::Tensor predict(torch::Tensor x) { + model->eval(); + torch::NoGradGuard no_grad; + x = x.to(device); + return model->forward(x); + } +}; + +// === 2. OctaveConformal (for path weighting) === +struct OctaveConformal { + std::vector> residuals; + std::vector weights = { 1.0, 0.7, 0.4 }; // 30d, 90d, 365d + torch::Device device; + + OctaveConformal() : device(torch::kCPU) { + if (torch::cuda::is_available()) { + device = torch::Device(torch::kCUDA); + } + } + + void update(torch::nn::Linear model, const std::vector& windows) { + residuals.clear(); + for (size_t i = 0; i < windows.size(); ++i) { + std::deque res; + auto& w = windows[i]; + for (size_t j = 0; j < w.X.size(); ++j) { + // Move tensor to device before forward pass + auto x = w.X[j].to(device); + auto out = model->forward(x); + double mean = out[0][0].item(); + double true_y = w.y[j].item(); + res.push_back(std::abs(true_y - mean)); + } + residuals.push_back(res); + } + } + + double p_value(double residual) { + double count = 0, total = 0; + for (size_t i = 0; i < residuals.size(); ++i) { + double w = weights[i]; + for (double r : residuals[i]) { + if (r <= residual) count += w; + total += w; + } + } + return (count + 1.0) / (total + 1.0); + } +}; + +// === 3. Monte Carlo Path Generator === +std::vector> monte_carlo_forecast( + torch::nn::Linear model, + torch::Tensor current_x, + int steps, + int paths, + OctaveConformal& conformal, + std::mt19937& rng, + const torch::Device& device +) { + std::vector> all_paths(paths, std::vector(steps)); + std::normal_distribution noise(0, 1); + + for (int p = 0; p < paths; ++p) { + torch::Tensor x = current_x.clone().to(device); + for (int t = 0; t < steps; ++t) { + model->eval(); + auto out = model->forward(x); + double mean = out[0][0].item(); + double logvar = out[0][1].item(); + double sigma = std::exp(0.5 * logvar); + double y = mean + sigma * noise(rng); + all_paths[p][t] = y; + + // Update input for next step (autoregressive) + auto new_val = torch::tensor({ {y} }, torch::TensorOptions().device(device)); + x = torch::cat({ x.narrow(1, 1, x.size(1) - 1), new_val }, 1); + } + } + return all_paths; +} + + +TEST(Torch, CheckForCuda) { + std::cout << "=== CUDA Diagnostics ===" << std::endl; + std::cout << "PyTorch version: " << TORCH_VERSION_MAJOR << "." + << TORCH_VERSION_MINOR << "." << TORCH_VERSION_PATCH << std::endl; + std::cout << "CUDA available: " << torch::cuda::is_available() << std::endl; + std::cout << "cuDNN available: " << torch::cuda::cudnn_is_available() << std::endl; + + if (torch::cuda::is_available()) { + std::cout << "CUDA devices: " << torch::cuda::device_count() << std::endl; + for (int i = 0; i < torch::cuda::device_count(); ++i) { + std::cout << "CUDA device " << i << std::endl; + } + } else { + std::cout << "CUDA is not available. Common causes:" << std::endl; + std::cout << "1. LibTorch was built without CUDA support" << std::endl; + std::cout << "2. CUDA runtime not installed or incompatible version" << std::endl; + std::cout << "3. GPU driver issues" << std::endl; + std::cout << "4. Missing environment variables" << std::endl; + } + std::cout << "========================" << std::endl; + + return 0; +} + +TEST(Torch, Basic) { + return 0; + try { + // Check CUDA availability + std::cout << "PyTorch version: " << TORCH_VERSION_MAJOR << "." + << TORCH_VERSION_MINOR << "." << TORCH_VERSION_PATCH << std::endl; + std::cout << "CUDA available: " << torch::cuda::is_available() << std::endl; + + if (torch::cuda::is_available()) { + std::cout << "CUDA devices: " << torch::cuda::device_count() << std::endl; + for (int i = 0; i < torch::cuda::device_count(); ++i) { + std::cout << "CUDA device " << i << std::endl; + } + } + + std::mt19937 rng(42); + std::normal_distribution noise(0, 1); + int input_dim = 5; // [lag1, lag2, bed, bath, trend] + int steps_ahead = 30; + int paths = 1000; + + OctaveTrainer trainer(input_dim); + OctaveConformal conformal; + + // Simulate time series + std::vector true_series; + torch::Tensor x = torch::zeros({ 1, input_dim }); + + // Set device for initial tensor + if (torch::cuda::is_available()) { + x = x.to(torch::kCUDA); + } + + for (int t = 0; t < 600; ++t) { + double trend = 200 + 50 * std::sin(t / 365.0 * M_PI); + double shock = (t == 300) ? -80 : (t == 500) ? +60 : 0; + double price = trend + shock + 15 * noise(rng); + true_series.push_back(price); + + torch::Tensor target = torch::tensor({ {price} }); + // Move target to the same device as model + if (torch::cuda::is_available()) { + target = target.to(torch::kCUDA); + } + trainer.train_step(x, target); + + if (t >= 365) { + conformal.update(trainer.model, trainer.windows); + if (t == 500) { + std::cout << "FORECASTING 30 STEPS AHEAD FROM t=" << t << "\n"; + auto future_paths = monte_carlo_forecast( + trainer.model, x, steps_ahead, paths, conformal, rng, + torch::cuda::is_available() ? torch::kCUDA : torch::kCPU + ); + + // Weigh paths with conformal p-value + std::vector weights(paths); + double sum_w = 0; + for (int p = 0; p < paths; ++p) { + double path_residual = 0; + for (int s = 0; s < std::min(5, steps_ahead); ++s) + path_residual += std::abs(future_paths[p][s] - future_paths[p][0]); + path_residual /= 5; + weights[p] = conformal.p_value(path_residual); + sum_w += weights[p]; + } + for (auto& w : weights) w /= sum_w; + + // Compute 90% weighted prediction band + std::vector> sorted(steps_ahead); + for (int s = 0; s < steps_ahead; ++s) { + std::vector> vals; + for (int p = 0; p < paths; ++p) + vals.emplace_back(future_paths[p][s], weights[p]); + std::sort(vals.begin(), vals.end()); + double cum = 0; + for (auto& [v, w] : vals) { + cum += w; + if (cum >= 0.05) { sorted[s].push_back(v); break; } + } + cum = 0; + for (auto it = vals.rbegin(); it != vals.rend(); ++it) { + cum += it->second; + if (cum >= 0.05) { sorted[s].push_back(it->first); break; } + } + } + + // Output + std::ofstream out("forecast.csv"); + out << "step,true,lower,upper,mean\n"; + for (int s = 0; s < steps_ahead; ++s) { + double mean = 0; + for (int p = 0; p < paths; ++p) mean += future_paths[p][s] * weights[p]; + double true_val = (t + s < true_series.size()) ? true_series[t + s] : NAN; + out << s << "," << true_val << "," << sorted[s][0] << "," << sorted[s][1] << "," << mean << "\n"; + std::cout << "t+" << std::setw(2) << s + << " | True: $" << std::fixed << std::setprecision(1) << true_val + << "k | Band: [$" << sorted[s][0] << "k, $" << sorted[s][1] << "k]\n"; + } + std::cout << "\nforecast.csv written.\n"; + break; + } + } + + // Shift input window + x = torch::cat({ x.narrow(1, 1, input_dim - 1), target }, 1); + } + } + catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + + + return 0; + +} + +#endif + diff --git a/packages/nodegraph/include/nodegraph/NodeGraphSchema.h b/packages/nodegraph/include/nodegraph/NodeGraphSchema.h index 9e5c6543..2ce77a8a 100644 --- a/packages/nodegraph/include/nodegraph/NodeGraphSchema.h +++ b/packages/nodegraph/include/nodegraph/NodeGraphSchema.h @@ -35,7 +35,7 @@ namespace l::nodegraph { class TreeMenuNode { public: TreeMenuNode() = default; - TreeMenuNode(std::string_view pathPart, std::string_view name, int32_t id) : mPathPart(pathPart), mName(name), mId(id) {} + TreeMenuNode(std::string_view pathPart, std::string_view name, int32_t id, std::string_view description = "") : mPathPart(pathPart), mName(name), mId(id), mDescription(description) {} ~TreeMenuNode() = default; std::string_view GetPathPart() const { @@ -47,27 +47,35 @@ namespace l::nodegraph { int32_t GetId() const { return mId; } + std::string_view GetDescription() const { + return mDescription; + } std::vector mChildren; std::string mPathPart; protected: std::string mName; int32_t mId = 0; + std::string mDescription; }; TreeMenuNode* findOrCreateChild(TreeMenuNode& node, std::string_view pathPart); - void insertPath(TreeMenuNode& root, std::string_view path, std::string_view name, int32_t nodeId); + void insertPath(TreeMenuNode& root, std::string_view path, std::string_view name, int32_t nodeId, std::string_view description); struct UINodeDesc { std::string_view GetName() const { return mName; } + std::string_view GetDescription() const { + return mDescription; + } int32_t GetId() const { return mId; } int32_t mId; std::string mName; + std::string mDescription; }; class NodeGraphSchema : public l::serialization::JsonSerializationBase, public NodeFactoryBase { @@ -102,9 +110,11 @@ namespace l::nodegraph { mMainNodeGraph.SetNodeFactory(this); // must set anew since schema (this) was moved as well mRegisteredNodeTypes = std::move(other.mRegisteredNodeTypes); mCustomNodeCreatorListeners = std::move(other.mCustomNodeCreatorListeners); +#ifndef HEADLESS_BUILD mKeyState = other.mKeyState; mAudioOutput = other.mAudioOutput; mMidiManager = other.mMidiManager; +#endif mRegisteredNodeTypes = std::move(other.mRegisteredNodeTypes); mPickerRootMenu = mPickerRootMenu; return *this; @@ -161,9 +171,11 @@ namespace l::nodegraph { virtual void GetArchiveData(l::serialization::JsonBuilder& jsonBuilder) override; void AddCustomNodeCreator(CustomCreateFunctionType customCreator); +#ifndef HEADLESS_BUILD void SetKeyState(l::hid::KeyState* keyState); void SetAudioOutput(l::audio::AudioStream* audioStream); void SetMidiManager(l::hid::midi::MidiManager* midiManager); +#endif int32_t NewNode(int32_t typeId, int32_t id = -1); bool RemoveNode(int32_t id); @@ -172,9 +184,22 @@ namespace l::nodegraph { void ForEachInputNode(std::function cb); void ForEachOutputNode(std::function cb); + template + void ForEachNodeOftype(std::function cb) { + mMainNodeGraph.ForEachNodeOftype(std::move(cb)); + } + template + void ForEachInputNodeOftype(std::function cb) { + mMainNodeGraph.ForEachInputNodeOftype(std::move(cb)); + } + template + void ForEachOutputNodeOftype(std::function cb) { + mMainNodeGraph.ForEachOutputNodeOftype(std::move(cb)); + } + bool HasNodeType(const std::string& typeGroup, int32_t typeId); - void ForEachNodeType(std::function&)> cb) const; - void RegisterNodeType(const std::string& typeGroup, int32_t uniqueTypeId, std::string_view typeName); + void ForEachNodeType(std::string_view search, std::function&)> cb) const; + void RegisterNodeType(const std::string& typeGroup, int32_t uniqueTypeId, std::string_view typeName, std::string_view description = ""); void RegisterAllOf(const std::string& typeGroup); void ProcessSubGraph(int32_t numSamples, int32_t numCacheSamples = -1); void Tick(int32_t tickCount, float delta); @@ -192,9 +217,11 @@ namespace l::nodegraph { NodeGraphGroup mMainNodeGraph; std::vector mCustomNodeCreatorListeners; +#ifndef HEADLESS_BUILD l::hid::KeyState* mKeyState = nullptr; l::audio::AudioStream* mAudioOutput = nullptr; l::hid::midi::MidiManager* mMidiManager = nullptr; +#endif std::map> mRegisteredNodeTypes; TreeMenuNode mPickerRootMenu; diff --git a/packages/nodegraph/include/nodegraph/core/NodeGraphBase.h b/packages/nodegraph/include/nodegraph/core/NodeGraphBase.h index 90528ae7..b754a79c 100644 --- a/packages/nodegraph/include/nodegraph/core/NodeGraphBase.h +++ b/packages/nodegraph/include/nodegraph/core/NodeGraphBase.h @@ -46,7 +46,7 @@ namespace l::nodegraph { mInputs.clear(); mOutputs.clear(); - LOG(LogInfo) << "Node graph base destroyed"; + //LLOG(LogInfo) << "Node graph base destroyed"; } NodeGraphBase& operator=(NodeGraphBase&& other) noexcept { @@ -86,11 +86,16 @@ namespace l::nodegraph { virtual int8_t GetNumInputs(); virtual int8_t GetNumOutputs(); + void SetOutputText(int8_t inputChannel, std::string_view text); + void SetOutput(int8_t inputChannel, float value); + virtual float& GetInput(int8_t inputChannel, int32_t minSize = 1, int32_t offset = 0); + virtual std::optional> GetInputBuffer(int8_t inputChannel); virtual std::string_view GetInputText(int8_t inputChannel, int32_t minSize = 16); virtual float& GetOutput(int8_t outputChannel, int32_t minSize = 1, int32_t offset = 0); - virtual std::string_view GetOutputText(int8_t outputChannel, int32_t minSize); + virtual std::optional> GetOutputBuffer(int8_t outputChannel); + virtual std::string_view GetOutputText(int8_t outputChannel, int32_t minSize = 16); virtual NodeGraphInput& GetInputOf(int8_t inputChannel); virtual NodeGraphOutput& GetOutputOf(int8_t outputChannel); @@ -129,6 +134,7 @@ namespace l::nodegraph { virtual void NodeHasChanged(); bool IsOutOfDate2(); virtual NodeType GetOutputType(); + virtual void RecieveEvent(int32_t id, int32_t cmd, void* userdata) = 0; template bool IsOfOperation() { @@ -186,7 +192,7 @@ namespace l::nodegraph { mName(name) {} virtual ~NodeGraphOp() { - LOG(LogInfo) << "Node operation destroyed"; + //LLOG(LogInfo) << "Node operation destroyed"; } NodeGraphOp& operator=(NodeGraphOp&& other) noexcept { @@ -218,6 +224,7 @@ namespace l::nodegraph { virtual void Process(int32_t, int32_t, std::vector&, std::vector&) {}; virtual void Tick(int32_t /*tickCount*/, float /*delta*/) {} virtual void InputHasChanged(); + virtual void RecieveEvent(int32_t, int32_t, void*) {} int8_t GetNumInputs(); int8_t GetNumOutputs(); @@ -241,8 +248,8 @@ namespace l::nodegraph { virtual int32_t AddInput(std::string_view name, float defaultValue = 0.0f, int32_t minSize = 1, float boundMin = -l::math::constants::FLTMAX, float boundMax = l::math::constants::FLTMAX, bool visible = true, bool editable = true); virtual int32_t AddOutput(std::string_view name, float defaultValue = 0.0f, int32_t minSize = 1, bool visible = true); virtual int32_t AddConstant(std::string_view name, float defaultValue = 0.0f, int32_t minSize = 1, float boundMin = -l::math::constants::FLTMAX, float boundMax = l::math::constants::FLTMAX, bool visible = true, bool editable = true); - virtual int32_t AddInput2(std::string_view name, int32_t minSize, InputFlags flags); - virtual int32_t AddOutput2(std::string_view name, int32_t minSize, OutputFlags flags); + virtual int32_t AddInput2(std::string_view name, int32_t minSize = 1, InputFlags flags = InputFlags(false, false, false, false)); + virtual int32_t AddOutput2(std::string_view name, int32_t minSize = 1, OutputFlags flags = OutputFlags(false, false)); NodeGraphBase* mNode = nullptr; std::string mName; @@ -265,7 +272,7 @@ namespace l::nodegraph { { } virtual ~NodeGraphOpCached() { - LOG(LogInfo) << "Buffered operation destroyed"; + //LLOG(LogInfo) << "Buffered operation destroyed"; } NodeGraphOpCached& operator=(NodeGraphOpCached&& other) noexcept { @@ -284,7 +291,7 @@ namespace l::nodegraph { void InputHasChanged() override { mInputHasChanged = true; - mWrittenSamples = 0; + //mWrittenSamples = 0; } protected: @@ -308,7 +315,7 @@ namespace l::nodegraph { DefaultDataInit(); } virtual ~NodeGraph() { - LOG(LogInfo) << "Node destroyed"; + //LLOG(LogInfo) << "Node destroyed"; } NodeGraph& operator=(NodeGraph&& other) noexcept { @@ -360,9 +367,9 @@ namespace l::nodegraph { if (tickCount <= mLastTickCount) { return; } + mLastTickCount = tickCount; NodeGraphBase::Tick(tickCount, delta); mOperation.Tick(tickCount, delta); - mLastTickCount = tickCount; } virtual std::string_view GetInputName(int8_t inputChannel) override { @@ -385,6 +392,13 @@ namespace l::nodegraph { return &mOperation; } + virtual void RecieveEvent(int32_t id, int32_t cmd, void* userdata) override { + auto op = GetOperation(); + if (op) { + op->RecieveEvent(id, cmd, userdata); + } + } + protected: T mOperation; }; diff --git a/packages/nodegraph/include/nodegraph/core/NodeGraphGroup.h b/packages/nodegraph/include/nodegraph/core/NodeGraphGroup.h index 22b3a1c2..436224b8 100644 --- a/packages/nodegraph/include/nodegraph/core/NodeGraphGroup.h +++ b/packages/nodegraph/include/nodegraph/core/NodeGraphGroup.h @@ -48,7 +48,7 @@ namespace l::nodegraph { } ~NodeGraphGroup() { Reset(); - LOG(LogInfo) << "Node group destroyed"; + //LLOG(LogInfo) << "Node group destroyed"; } NodeGraphGroup& operator=(NodeGraphGroup&& other) noexcept { @@ -131,9 +131,43 @@ namespace l::nodegraph { void ForEachInputNode(std::function cb); void ForEachOutputNode(std::function cb); + template + void ForEachNodeOftype(std::function cb) { + for (auto& it : mNodes) { + if (it->IsOfOperation()) { + if (!cb(it)) { + break; + } + } + } + } + template + void ForEachInputNodeOftype(std::function cb) { + for (auto& it : mInputNodes) { + if (it->IsOfOperation()) { + if (!cb(it)) { + break; + } + } + } + } + template + void ForEachOutputNodeOftype(std::function cb) { + for (auto& it : mOutputNodes) { + if (it->IsOfOperation()) { + if (!cb(it)) { + break; + } + } + } + } + + void ClearProcessFlags(); void ProcessSubGraph(int32_t numSamples, int32_t numCacheSamples = -1); void Tick(int32_t tickCount, float elapsed); + + void SendEvent(int32_t id, int32_t cmd = 0, void* userdata = nullptr); protected: NodeFactoryBase* mNodeFactory = nullptr; diff --git a/packages/nodegraph/include/nodegraph/core/NodeGraphInput.h b/packages/nodegraph/include/nodegraph/core/NodeGraphInput.h index bb53028d..97a7c7b9 100644 --- a/packages/nodegraph/include/nodegraph/core/NodeGraphInput.h +++ b/packages/nodegraph/include/nodegraph/core/NodeGraphInput.h @@ -68,6 +68,7 @@ namespace l::nodegraph { void MinimizeBuffer(int32_t size); float& Get(int32_t minSize = 1, int32_t offset = 0); + std::optional> GetBuffer(); float& GetArray(int32_t minSize = 1, int32_t offset = 0); std::string_view GetText(int32_t minSize = 16); NodeGraphBase* GetInputNode(); diff --git a/packages/nodegraph/include/nodegraph/core/NodeGraphOutput.h b/packages/nodegraph/include/nodegraph/core/NodeGraphOutput.h index 8227dcf5..f51cbbfc 100644 --- a/packages/nodegraph/include/nodegraph/core/NodeGraphOutput.h +++ b/packages/nodegraph/include/nodegraph/core/NodeGraphOutput.h @@ -38,6 +38,7 @@ namespace l::nodegraph { void Clear(); void MinimizeBuffer(int32_t size); float& Get(int32_t minSize = 1, int32_t offset = 0); + std::optional> GetBuffer(); std::string_view GetText(int32_t minSize = 16); void SetText(std::string_view text); NodeDataIterator GetIterator(int32_t minSize, float lod = 1.0f); diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpDataIO.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpDataIO.h index 1a29f7a0..409747ec 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpDataIO.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpDataIO.h @@ -3,12 +3,6 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpDeviceIOInput.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpDeviceIOInput.h index 4a486283..1d1704bc 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpDeviceIOInput.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpDeviceIOInput.h @@ -3,12 +3,14 @@ #include "logging/LoggingAll.h" +#ifndef HEADLESS_BUILD #include "hid/KeyboardPiano.h" #include "hid/KeyState.h" #include "hid/Midi.h" #include "audio/PortAudio.h" #include "audio/AudioUtils.h" +#endif #include "math/MathFunc.h" @@ -23,6 +25,7 @@ namespace l::nodegraph { +#ifndef HEADLESS_BUILD /*********************************************************************/ class GraphInputKeyboardPiano : public NodeGraphOp, public l::audio::INoteProcessor { public: @@ -352,6 +355,7 @@ namespace l::nodegraph { l::audio::FilterRWA mFilterEnvelope; }; +#endif // HEADLESS_BUILD } diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpDeviceIOOutput.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpDeviceIOOutput.h index cc025f12..c02b8702 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpDeviceIOOutput.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpDeviceIOOutput.h @@ -3,11 +3,13 @@ #include "logging/LoggingAll.h" +#ifndef HEADLESS_BUILD #include "hid/KeyboardPiano.h" #include "hid/Midi.h" #include "audio/PortAudio.h" #include "audio/AudioUtils.h" +#endif #include "math/MathFunc.h" @@ -41,6 +43,7 @@ namespace l::nodegraph { float mValue = 0.0F; }; +#ifndef HEADLESS_BUILD /*********************************************************************/ class GraphOutputSpeaker : public NodeGraphOp { public: @@ -69,6 +72,7 @@ namespace l::nodegraph { float mRelease = 1.0f; l::audio::FilterRWA mFilterEnvelope; }; +#endif // HEADLESS_BUILD /*********************************************************************/ class GraphOutputPlot : public NodeGraphOp { diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpMathAritmethic.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpMathAritmethic.h index 165b46e0..0eaedc61 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpMathAritmethic.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpMathAritmethic.h @@ -3,12 +3,6 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include @@ -29,18 +23,15 @@ namespace l::nodegraph { MathAritmethicAdd(NodeGraphBase* node) : NodeGraphOp(node, "Add") { - AddInput("In1"); - AddInput("In2"); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddOutput("In1+In2"); + AddInput("a"); + AddInput("b"); + AddOutput("a+b"); } virtual ~MathAritmethicAdd() = default; virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { auto input0 = inputs.at(0).GetIterator(numSamples); auto input1 = inputs.at(1).GetIterator(numSamples); - auto lodExp = inputs.at(2).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto output = outputs.at(0).GetIterator(numSamples, lodFactor); + auto output = &outputs.at(0).Get(numSamples); for (int32_t i = 0; i < numSamples; i++) { *output++ = *input0++ + *input1++; @@ -54,19 +45,16 @@ namespace l::nodegraph { MathAritmethicMultiply(NodeGraphBase* node) : NodeGraphOp(node, "Multiply") { - AddInput("In1"); - AddInput("In2"); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddOutput("In1*In2"); + AddInput("a"); + AddInput("b"); + AddOutput("a*b"); } virtual ~MathAritmethicMultiply() = default; virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { auto input0 = inputs.at(0).GetIterator(numSamples); auto input1 = inputs.at(1).GetIterator(numSamples); - auto lodExp = inputs.at(2).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto output = outputs.at(0).GetIterator(numSamples, lodFactor); + auto output = &outputs.at(0).Get(numSamples); for (int32_t i = 0; i < numSamples; i++) { *output++ = *input0++ * *input1++; @@ -80,20 +68,17 @@ namespace l::nodegraph { MathAritmethicSubtract(NodeGraphBase* node) : NodeGraphOp(node, "Subtract") { - AddInput("In1"); - AddInput("In2"); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddOutput("In1-In2"); - AddOutput("In2-In1"); + AddInput("a"); + AddInput("b"); + AddOutput("a-b"); + AddOutput("b-a"); } virtual ~MathAritmethicSubtract() = default; virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { auto input0 = inputs.at(0).GetIterator(numSamples); auto input1 = inputs.at(1).GetIterator(numSamples); - auto lodExp = inputs.at(2).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto output1 = outputs.at(0).GetIterator(numSamples, lodFactor); - auto output2 = outputs.at(1).GetIterator(numSamples, lodFactor); + auto output1 = &outputs.at(0).Get(numSamples); + auto output2 = &outputs.at(1).Get(numSamples); for (int32_t i = 0; i < numSamples; i++) { auto diff = *input0++ - *input1++; @@ -109,17 +94,14 @@ namespace l::nodegraph { MathAritmethicNegate(NodeGraphBase* node) : NodeGraphOp(node, "Negate") { - AddInput("In"); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddOutput("-In"); + AddInput("x"); + AddOutput("-x"); } virtual ~MathAritmethicNegate() = default; virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { auto input0 = inputs.at(0).GetIterator(numSamples); - auto lodExp = inputs.at(1).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto output = outputs.at(0).GetIterator(numSamples, lodFactor); + auto output = &outputs.at(0).Get(numSamples); for (int32_t i = 0; i < numSamples; i++) { *output++ = -*input0++; @@ -134,21 +116,18 @@ namespace l::nodegraph { MathAritmethicAbs(NodeGraphBase* node) : NodeGraphOp(node, "Abs") { - AddInput("In"); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddOutput("abs(In)"); - AddOutput("max(In,0)"); - AddOutput("min(In,0)"); + AddInput("x"); + AddOutput("abs(x)"); + AddOutput("max(x,0)"); + AddOutput("min(x,0)"); } virtual ~MathAritmethicAbs() = default; virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { auto input0 = inputs.at(0).GetIterator(numSamples); - auto lodExp = inputs.at(1).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto output1 = outputs.at(0).GetIterator(numSamples, lodFactor); - auto output2 = outputs.at(1).GetIterator(numSamples, lodFactor); - auto output3 = outputs.at(2).GetIterator(numSamples, lodFactor); + auto output1 = &outputs.at(0).Get(numSamples); + auto output2 = &outputs.at(1).Get(numSamples); + auto output3 = &outputs.at(2).Get(numSamples); for (int32_t i = 0; i < numSamples; i++) { auto in = *input0++; @@ -165,21 +144,18 @@ namespace l::nodegraph { MathAritmethicLog(NodeGraphBase* node) : NodeGraphOp(node, "Log") { - AddInput("In"); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddInput("Base", 2.72f, 1, 1.0f, 10.0f); - AddOutput("ln(In)"); - AddOutput("ln(In)/ln(Base)"); + AddInput("x"); + AddInput("b", 2.72f, 1, 1.0f, 10.0f); + AddOutput("Log"); + AddOutput("Logb"); } virtual ~MathAritmethicLog() = default; virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { auto input0 = inputs.at(0).GetIterator(numSamples); - auto lodExp = inputs.at(1).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto base = inputs.at(2).Get(); - auto output1 = outputs.at(0).GetIterator(numSamples, lodFactor); - auto output2 = outputs.at(1).GetIterator(numSamples, lodFactor); + auto base = inputs.at(1).Get(); + auto output1 = &outputs.at(0).Get(numSamples); + auto output2 = &outputs.at(1).Get(numSamples); // we want logb(in) = ln(in)/ln(b) // so precalc base factor 1/ln(b) @@ -198,13 +174,12 @@ namespace l::nodegraph { class MathAritmethicMultiply3 : public NodeGraphOp { public: MathAritmethicMultiply3(NodeGraphBase* node) : - NodeGraphOp(node, "Multiply3") + NodeGraphOp(node, "Multiply 3") { - AddInput("In1"); - AddInput("In2"); - AddInput("In3"); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddOutput("In1*In2*In3"); + AddInput("a"); + AddInput("b"); + AddInput("c"); + AddOutput("abc"); } virtual ~MathAritmethicMultiply3() = default; @@ -212,9 +187,7 @@ namespace l::nodegraph { auto input0 = inputs.at(0).GetIterator(numSamples); auto input1 = inputs.at(1).GetIterator(numSamples); auto input2 = inputs.at(2).GetIterator(numSamples); - auto lodExp = inputs.at(3).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto output = outputs.at(0).GetIterator(numSamples, lodFactor); + auto output = &outputs.at(0).Get(numSamples); for (int32_t i = 0; i < numSamples; i++) { *output++ = *input0++ * *input1++ * *input2++; @@ -228,11 +201,10 @@ namespace l::nodegraph { MathAritmethicMultiplyAndAdd(NodeGraphBase* node) : NodeGraphOp(node, "Multiply & Add") { - AddInput("In1"); - AddInput("In2"); - AddInput("In3"); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddOutput("In1*In2+In3"); + AddInput("a"); + AddInput("b"); + AddInput("c"); + AddOutput("ab+c"); } virtual ~MathAritmethicMultiplyAndAdd() = default; @@ -240,9 +212,7 @@ namespace l::nodegraph { auto input0 = inputs.at(0).GetIterator(numSamples); auto input1 = inputs.at(1).GetIterator(numSamples); auto input2 = inputs.at(2).GetIterator(numSamples); - auto lodExp = inputs.at(3).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto output = outputs.at(0).GetIterator(numSamples, lodFactor); + auto output = &outputs.at(0).Get(numSamples); for (int32_t i = 0; i < numSamples; i++) { *output++ = *input0++ * *input1++ + *input2++; @@ -256,9 +226,8 @@ namespace l::nodegraph { MathAritmethicRound(NodeGraphBase* node) : NodeGraphOp(node, "Round") { - AddInput("In"); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddOutput("int(In+0.5)"); + AddInput("x"); + AddOutput("Out"); } virtual ~MathAritmethicRound() = default; @@ -266,9 +235,7 @@ namespace l::nodegraph { outputs.at(0).mOutput = l::math::round(inputs.at(0).Get()); auto input0 = inputs.at(0).GetIterator(numSamples); - auto lodExp = inputs.at(1).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto output = outputs.at(0).GetIterator(numSamples, lodFactor); + auto output = &outputs.at(0).Get(numSamples); for (int32_t i = 0; i < numSamples; i++) { *output++ = l::math::round(*input0++); @@ -276,4 +243,180 @@ namespace l::nodegraph { } }; + /*********************************************************************/ + class MathAritmethicPow : public NodeGraphOp { + public: + MathAritmethicPow(NodeGraphBase* node) : + NodeGraphOp(node, "Pow") + { + AddInput("x"); + AddInput("y", 2.72f, 1, 1.0f, 10.0f); + AddOutput("x^y"); + } + + virtual ~MathAritmethicPow() = default; + virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { + auto input0 = inputs.at(0).GetIterator(numSamples); + auto exponent = inputs.at(1).Get(); + auto output1 = &outputs.at(0).Get(numSamples); + + for (int32_t i = 0; i < numSamples; i++) { + auto in = *input0++; + auto pow = l::math::pow(in, exponent); + *output1++ = pow; + } + } + }; + + /*********************************************************************/ + class MathAritmethicSum3 : public NodeGraphOp { + public: + MathAritmethicSum3(NodeGraphBase* node) : + NodeGraphOp(node, "Sum 3") + { + AddInput("a"); + AddInput("b"); + AddInput("c"); + AddOutput("a+b+c"); + } + virtual ~MathAritmethicSum3() = default; + virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { + auto input0 = inputs.at(0).GetIterator(numSamples); + auto input1 = inputs.at(1).GetIterator(numSamples); + auto input2 = inputs.at(2).GetIterator(numSamples); + auto output = &outputs.at(0).Get(numSamples); + + for (int32_t i = 0; i < numSamples; i++) { + *output++ = *input0++ + *input1++ + *input2++; + } + } + }; + + /*********************************************************************/ + class MathAritmethicSum5 : public NodeGraphOp { + public: + MathAritmethicSum5(NodeGraphBase* node) : + NodeGraphOp(node, "Sum 5") + { + AddInput("a"); + AddInput("b"); + AddInput("c"); + AddInput("d"); + AddInput("e"); + AddOutput("sum"); + } + virtual ~MathAritmethicSum5() = default; + virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { + auto input0 = inputs.at(0).GetIterator(numSamples); + auto input1 = inputs.at(1).GetIterator(numSamples); + auto input2 = inputs.at(2).GetIterator(numSamples); + auto input3 = inputs.at(3).GetIterator(numSamples); + auto input4 = inputs.at(4).GetIterator(numSamples); + auto output = &outputs.at(0).Get(numSamples); + + for (int32_t i = 0; i < numSamples; i++) { + *output++ = *input0++ + *input1++ + *input2++ + *input3++ + *input4++; + } + } + }; + + /*********************************************************************/ + class MathAritmethicMinMax : public NodeGraphOp { + public: + MathAritmethicMinMax(NodeGraphBase* node) : + NodeGraphOp(node, "Minmax 1") + { + AddInput("In"); + AddInput("Min"); + AddInput("Max"); + + AddOutput(">=<"); + AddOutput(">="); + AddOutput("<="); + } + virtual ~MathAritmethicMinMax() = default; + virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { + auto inInput = inputs.at(0).GetIterator(numSamples); + auto min = inputs.at(1).Get(); + auto max = inputs.at(2).Get(); + auto minmaxOutput = &outputs.at(0).Get(numSamples); + auto minOutput = &outputs.at(1).Get(numSamples); + auto maxOutput = &outputs.at(2).Get(numSamples); + + for (int32_t i = 0; i < numSamples; i++) { + auto in = *inInput++; + if (min < max) { + // min max contains legal values + *minmaxOutput++ = in < min ? min : (in > max ? max : in); + } + else { + // min max excludes legal values + *minmaxOutput++ = in >= min ? in : (in >= max ? min : in); + } + *minOutput++ = in >= min ? in : min; + *maxOutput++ = in <= max ? in : max; + } + } + }; + + /*********************************************************************/ + class MathAritmethicMinMax2 : public NodeGraphOp { + public: + MathAritmethicMinMax2(NodeGraphBase* node) : + NodeGraphOp(node, "Minmax 2") + { + AddInput("In1"); + AddInput("In2"); + + AddOutput("Min"); + AddOutput("Max"); + } + virtual ~MathAritmethicMinMax2() = default; + virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { + auto in1Input = inputs.at(0).GetIterator(numSamples); + auto in2Input = inputs.at(1).GetIterator(numSamples); + auto minOutput = &outputs.at(0).Get(numSamples); + auto maxOutput = &outputs.at(1).Get(numSamples); + + for (int32_t i = 0; i < numSamples; i++) { + auto in1 = *in1Input++; + auto in2 = *in2Input++; + + *minOutput++ = in1 < in2 ? in1 : in2; + *maxOutput++ = in1 > in2 ? in1 : in2; + } + } + }; + + /*********************************************************************/ + class MathAritmethicDiv : public NodeGraphOp { + public: + MathAritmethicDiv(NodeGraphBase* node) : + NodeGraphOp(node, "Div") + { + AddInput("In1"); + AddInput("In2"); + + AddOutput("Out"); + } + virtual ~MathAritmethicDiv() = default; + virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { + auto in1Input = inputs.at(0).GetIterator(numSamples); + auto in2Input = inputs.at(1).GetIterator(numSamples); + auto outOutput = &outputs.at(0).Get(numSamples); + + for (int32_t i = 0; i < numSamples; i++) { + auto in1 = *in1Input++; + auto in2 = *in2Input++; + + auto out = 0.0f; + if (in2 != 0.0f) { + out = in1 / in2; + } + + *outOutput++ = out; + } + } + }; + } \ No newline at end of file diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpMathLogic.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpMathLogic.h index f7ebce51..a27fe83b 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpMathLogic.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpMathLogic.h @@ -3,12 +3,6 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpMathNumerical.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpMathNumerical.h index ab0a314d..f6338457 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpMathNumerical.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpMathNumerical.h @@ -3,17 +3,12 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include #include #include +#include #include #include #include @@ -29,86 +24,88 @@ namespace l::nodegraph { MathNumericalIntegral(NodeGraphBase* node) : NodeGraphOp(node, "Integral") { - AddInput("In", 0.0f, 1); + AddInput2("x"); AddInput("Friction", 1.0f, 1, 0.0f, 1.0f); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddOutput("Out", 0.0f, 1); + AddOutput2("Intgr(x)"); } virtual ~MathNumericalIntegral() = default; - virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override { - auto input0 = inputs.at(0).GetIterator(numSamples); - auto friction = inputs.at(1).Get(); - auto frictionFactor = l::math::clamp(l::math::pow(friction, 0.25f), 0.0f, 1.0f); - auto lodExp = inputs.at(2).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto output = outputs.at(0).GetIterator(numSamples, lodFactor); - - for (int32_t i = 0; i < numSamples; i++) { - mOutput += *input0++; - mOutput *= frictionFactor; - *output++ = mOutput; - } + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + virtual void Reset() override { + mOutput = 0.0f; + } + protected: + int32_t mReadSamples = 0; - mReadSamples += numSamples; + float mOutput = 0.0f; + }; - if (mReadSamples >= numCacheSamples) { - mReadSamples = 0; - mOutput = 0.0f; - } + /*********************************************************************/ + class MathNumericalTemporalChange1 : public NodeGraphOp { + public: + MathNumericalTemporalChange1(NodeGraphBase* node) : + NodeGraphOp(node, "Change 1") + { + AddInput2("in"); + AddOutput2("out"); + } - if (isnan(mOutput)) { - mOutput = 0.0f; - } + virtual ~MathNumericalTemporalChange1() = default; + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + protected: + int32_t mReadSamples = 0; + float mInputPrev = 0.0f; + }; + /*********************************************************************/ + class MathNumericalTemporalChange2 : public NodeGraphOp { + public: + MathNumericalTemporalChange2(NodeGraphBase* node) : + NodeGraphOp(node, "Change 2") + { + AddInput2("in"); + AddOutput2("out"); } - virtual void Reset() override { - mOutput = 0.0f; - } + + virtual ~MathNumericalTemporalChange2() = default; + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; protected: int32_t mReadSamples = 0; - float mOutput = 0.0f; + float mInputPrev = 0.0f; }; /*********************************************************************/ - class MathNumericalDerivate : public NodeGraphOp { + class MathNumericalDiff2 : public NodeGraphOp { public: - MathNumericalDerivate(NodeGraphBase* node) : - NodeGraphOp(node, "Derivate") + MathNumericalDiff2(NodeGraphBase* node) : + NodeGraphOp(node, "Difference 2") { - AddInput("In", 0.0f, 1); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddOutput("Out", 0.0f, 1); + AddInput2("In"); + AddOutput2("Diff 2"); } - virtual ~MathNumericalDerivate() = default; - virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override { - auto input0 = inputs.at(0).GetIterator(numSamples); - auto lodExp = inputs.at(1).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto output = outputs.at(0).GetIterator(numSamples, lodFactor); - - for (int32_t i = 0; i < numSamples; i++) { - float input = *input0++; - float value = input - mInputPrev; - float divisor = l::math::abs(input) + l::math::abs(mInputPrev); - if (divisor > 0.0f) { - value = 2.0f * value / divisor; - } - mInputPrev = input; - *output++ = value; - } - - mReadSamples += numSamples; - - if (mReadSamples >= numCacheSamples) { - mReadSamples = 0; - mInputPrev = 0.0f; - } + virtual ~MathNumericalDiff2() = default; + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + protected: + int32_t mReadSamples = 0; + + float mInputPrev = 0.0f; + }; + /*********************************************************************/ + class MathNumericalDiff1 : public NodeGraphOp { + public: + MathNumericalDiff1(NodeGraphBase* node) : + NodeGraphOp(node, "Difference 1") + { + AddInput2("In"); + AddOutput2("Diff"); } + + virtual ~MathNumericalDiff1() = default; + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; protected: int32_t mReadSamples = 0; @@ -116,94 +113,222 @@ namespace l::nodegraph { }; /*********************************************************************/ - class MathNumericalDiffNorm : public NodeGraphOp { + class MathNumericalLevelTrigger : public NodeGraphOp { public: - MathNumericalDiffNorm(NodeGraphBase* node) : - NodeGraphOp(node, "Difference Normalized") + MathNumericalLevelTrigger(NodeGraphBase* node) : + NodeGraphOp(node, "Level Trigger") { - AddInput("In", 0.0f, 1); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddOutput("Out", 0.0f, 1); + AddInput2("In"); + AddInput("Max", 1.0f); + AddInput("Min", -1.0f); + AddInput("Num levels", 1.0f, 1, 1.0f, 100.0f, true, true); + AddInput("Max", 1.0f, 1, 0.0f, 3.0f, true, true); + AddInput("Min", 0.0f, 1, -3.0f, 1.0f, true, true); + AddOutput2("Level"); + AddOutput2("Pulse"); } - virtual ~MathNumericalDiffNorm() = default; - virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override { - auto input0 = inputs.at(0).GetIterator(numSamples); - auto lodExp = inputs.at(1).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto output = outputs.at(0).GetIterator(numSamples, lodFactor); - - for (int32_t i = 0; i < numSamples; i++) { - float input = *input0++; - float value = mInputPrev; - if (mInputPrev != 0.0f) { - if (input > 0.0f && mInputPrev > 0.0f) { - value = input / mInputPrev; - value = value - 1.0f; - } - else if (input < 0.0f && mInputPrev < 0.0f) { - value = input / mInputPrev; - value = (value - 1.0f); - } - else { - value = 0.0f; - } - } - mInputPrev = input; - *output++ = value; - } - - mReadSamples += numSamples; - - if (mReadSamples >= numCacheSamples) { - mReadSamples = 0; - mInputPrev = 0.0f; - } + virtual ~MathNumericalLevelTrigger() = default; + virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override; + protected: + float mLevelPrev = 0.0f; + }; + /*********************************************************************/ + class MathNumericalMinMaxChannel : public NodeGraphOp { + public: + MathNumericalMinMaxChannel(NodeGraphBase* node) : + NodeGraphOp(node, "Minmax Channel") + { + AddInput("Max", 1.0f, 1); + AddInput("Min", 0.0f, 1); + AddInput2("In"); + AddInput("Friction", 1.0f, 1, 0.0f, 1.0f); + + AddOutput2("Range"); + AddOutput2("Range Max"); + AddOutput2("Range Min"); + AddOutput2("Range Norm"); } + + virtual ~MathNumericalMinMaxChannel() = default; + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; protected: int32_t mReadSamples = 0; + float mCurRangeMax = 0.0f; + float mCurRangeMin = 0.0f; + }; + + /*********************************************************************/ + class MathNumericalReconstructor1 : public NodeGraphOp { + public: + MathNumericalReconstructor1(NodeGraphBase* node) : + NodeGraphOp(node, "Reconstructor 1") + { + AddInput2("In"); + AddInput("Base", 0.0f, 1); + AddInput("Friction1", 1.0f, 1, 0.0f, 1.0f); + AddInput("Friction2", 1.0f, 1, 0.0f, 1.0f); + AddInput("Scale1", 1.0f, 1, 0.0f, 100.0f); + AddInput("Scale2", 1.0f, 1, 0.0f, 100.0f); + + AddOutput2("Diff"); + AddOutput2("Diff+base"); + AddOutput2("Intgr1"); + AddOutput2("Intgr+base"); + AddOutput2("Intgr2"); + AddOutput2("Intgr2+base"); + } + + virtual ~MathNumericalReconstructor1() = default; + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + protected: + int32_t mReadSamples = 0; float mInputPrev = 0.0f; + float mDiffPrev = 0.0f; + float mOutput1 = 0.0f; + float mOutput2 = 0.0f; }; /*********************************************************************/ - class MathNumericalDiff : public NodeGraphOp { + class MathNumericalReconstructor2 : public NodeGraphOp { public: - MathNumericalDiff(NodeGraphBase* node) : - NodeGraphOp(node, "Difference") + MathNumericalReconstructor2(NodeGraphBase* node) : + NodeGraphOp(node, "Reconstructor 2") { - AddInput("In", 0.0f, 1); - AddInput("Lod", 0.0f, 1, 0.0f, 1.0f); - AddOutput("Out", 0.0f, 1); + AddInput2("In1"); + AddInput2("In2"); + AddInput("Friction1", 1.0f, 1, 0.0f, 1.0f); + AddInput("Friction2", 1.0f, 1, 0.0f, 1.0f); + + AddOutput2("Intgr1"); + AddOutput2("Intgr2"); + AddOutput2("Intgr Both"); } - virtual ~MathNumericalDiff() = default; - virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override { - auto input0 = inputs.at(0).GetIterator(numSamples); - auto lodExp = inputs.at(1).Get(); - auto lodFactor = l::math::pow(2.0f, l::math::round(lodExp)); - auto output = outputs.at(0).GetIterator(numSamples, lodFactor); + virtual ~MathNumericalReconstructor2() = default; + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + protected: + int32_t mReadSamples = 0; - for (int32_t i = 0; i < numSamples; i++) { - float input = *input0++; - float value = input - mInputPrev; - mInputPrev = input; - *output++ = value; - } + float mInPrev1 = 0.0f; + float mIn1Prev1 = 0.0f; + float mIn2Prev1 = 0.0f; - mReadSamples += numSamples; + float mInAccum = 0.0f; + float mIn1Accum = 0.0f; + float mIn2Accum = 0.0f; + float mInAccumPrev1 = 0.0f; + float mIn1AccumPrev1 = 0.0f; + float mIn2AccumPrev1 = 0.0f; + }; - if (mReadSamples >= numCacheSamples) { - mReadSamples = 0; - mInputPrev = 0.0f; - } + /*********************************************************************/ + class MathNumericalUnitmap : public nodegraph::NodeGraphOp { + public: + MathNumericalUnitmap(nodegraph::NodeGraphBase* node) : + NodeGraphOp(node, "Unitmap") + { + AddInput2("In"); + AddInput("Scale", 0.5f, 1, 0.0f, 100000.0f); + AddInput("Offset", 0.0f, 1, -1.0f, 1.0f); + AddOutput2("Out"); } + + virtual ~MathNumericalUnitmap() = default; + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + protected: + }; + + /*********************************************************************/ + class MathNumericalEMA : public nodegraph::NodeGraphOp { + public: + MathNumericalEMA(nodegraph::NodeGraphBase* node) : + NodeGraphOp(node, "EMA") + { + AddInput2("In"); + AddInput("N", 14.0f, 1, 1.0f, 1000.0f); + + AddOutput2("Out"); + } + + virtual ~MathNumericalEMA() = default; + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; protected: int32_t mReadSamples = 0; - float mInputPrev = 0.0f; + float mEmaAccum = 0.0f; + }; + + /*********************************************************************/ + class MathNumericalSMA : public nodegraph::NodeGraphOp { + public: + MathNumericalSMA(nodegraph::NodeGraphBase* node) : + NodeGraphOp(node, "SMA") + { + AddInput2("In"); + AddInput("N", 14.0f, 1, 1.0f, 2000.0f); + + AddOutput2("Out"); + } + + virtual ~MathNumericalSMA() = default; + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + protected: + int32_t mReadSamples = 0; + + float mSum = 0.0f; + std::deque mValues; }; + /*********************************************************************/ + class MathNumericalMeanExpRegression : public nodegraph::NodeGraphOp { + public: + MathNumericalMeanExpRegression(nodegraph::NodeGraphBase* node) : + NodeGraphOp(node, "Mean Regression") + { + AddInput2("In"); + AddInput("N", 14.0f, 1, 1.0f, 1000.0f); + AddInput("Exp", 2.0f, 1, 0.0f, 10.0f); + AddInput("Distribution", 2.0f, 1, 0.0f, 10.0f); + + AddOutput2("Mean"); + AddOutput2("Mean Exp"); + } + virtual ~MathNumericalMeanExpRegression() = default; + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + protected: + int32_t mReadSamples = 0; + + std::vector mValues; + }; + + /*********************************************************************/ + class MathNumericalStdDev : public nodegraph::NodeGraphOp { + public: + MathNumericalStdDev(nodegraph::NodeGraphBase* node) : + NodeGraphOp(node, "Standard Deviation") + { + AddInput2("In"); + AddInput("N", 1.0f, 1, 1.0f, 1000.0f); + AddInput("Band", 2.0f, 1, 0.0f, 10.0f); + + AddOutput2("Ewma"); + AddOutput2("Stddev"); + AddOutput2("Upper"); + AddOutput2("Lower"); + AddOutput2("Z-score"); + } + + virtual ~MathNumericalStdDev() = default; + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + protected: + int32_t mReadSamples = 0; + + float alpha = 0.0f; + float ema_prev = 0.0f; + float variance_ewma = 0.0; + }; } \ No newline at end of file diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalControl.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalControl.h index 0f828361..ae3a9877 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalControl.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalControl.h @@ -3,12 +3,6 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalEffect.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalEffect.h index 13148512..5e355aa0 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalEffect.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalEffect.h @@ -3,12 +3,6 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalFilter.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalFilter.h index 58b4a951..7f43eedb 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalFilter.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalFilter.h @@ -3,12 +3,6 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include @@ -135,8 +129,8 @@ namespace l::nodegraph { mInputManager.AddInput(InputIterationType::SAMPLED_ARRAY, AddInput("In")); mInputManager.AddInput(InputIterationType::SAMPLED_ARRAY, AddInput("Weight")); - AddInput("Kernel Size", 1.0f, 1, 1.0f, 5000.0f); - AddInput("Kernel Balance", 0.0f, 1, 0.0f, 10.0f); + AddInput("Kernel", 1.0f, 1, 1.0f, 5000.0f); + AddInput("Balance", 0.0f, 1, 0.0f, 10.0f); AddInput("Weight Accent", 1.0f, 1, 0.0f, 10.0f); AddInput("Gamma", 1.0f, 1, 0.0f, 10.0f); diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalGenerator.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalGenerator.h index 002b81a8..1030cadb 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalGenerator.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignalGenerator.h @@ -3,12 +3,6 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include @@ -253,7 +247,6 @@ namespace l::nodegraph { float mVolume = 0.0f; double mFmod = 0.0; double mPmod = 0.0; - float mReset = 0.0f; double mWave = 0.0; double mDeltaTime = 0.0; @@ -263,6 +256,7 @@ namespace l::nodegraph { double mPhaseFmod = 0.0; float mSamplesUntilUpdate = 0.0f; + int32_t mReadSamples = 0; }; /*********************************************************************/ diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSource.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSource.h index 9c717fa5..8376af82 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSource.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSource.h @@ -3,12 +3,6 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include @@ -112,5 +106,31 @@ namespace l::nodegraph { } virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; }; + + /*********************************************************************/ + class GraphSourceConstants2 : public NodeGraphOp { + public: + GraphSourceConstants2(NodeGraphBase* node) : + NodeGraphOp(node, "Constants2") + { + AddConstant("1"); + AddConstant("2"); + AddConstant("3"); + AddConstant("4"); + AddConstant("Min"); + AddConstant("Max"); + + AddOutput("Out 1"); + AddOutput("Out 2"); + AddOutput("Out 3"); + AddOutput("Out 3"); + } + + virtual ~GraphSourceConstants2() { + } + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + protected: + }; + } diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingDataIO.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingDataIO.h index 3c77afa6..ea9eec50 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingDataIO.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingDataIO.h @@ -26,21 +26,23 @@ namespace l::nodegraph { NodeGraphOpCached(node, "OCHLV Data In"), mMode(mode) { - if (mMode == 1) { - mName = "OCHLV Heikin-Ashi In"; - } + if (mMode == 1) { + mName = "OCHLV Heikin-Ashi In"; + } - AddInput("In", 0.0f, 2, -l::math::constants::FLTMAX, l::math::constants::FLTMAX, false, false); - AddInput2("Symbol", 16, InputFlags(false, true, false, true)); + AddInput2("In", 16, InputFlags(false, false, false, false)); + AddInput2("Symbol", 16, InputFlags(false, true, true, true)); AddInput2("Base", 16, InputFlags(false, true, false, true)); AddInput("Index", 2.0f, 1, 0.0f, 10.0f); + AddInput("Timeframe", 1.0f, 1, 1.0f, 1440.0f); + AddInput("Friction", 0.0f, 1, 0.0f, 1.0f); - AddOutput2("Symbol", 16, OutputFlags(true, true)); - AddOutput2("Base", 16, OutputFlags(true, true)); - AddOutput("Interval Min", 1.0f); + AddOutput2("Symbol", 16, OutputFlags(false, true)); + AddOutput2("Base", 16, OutputFlags(false, true)); + AddOutput("Min", 1.0f); - AddOutput("Unixtime", 0.0f, 2); + AddOutput("Time", 0.0f, 2); AddOutput("Open", 0.0f, 2); AddOutput("Close", 0.0f, 2); AddOutput("High", 0.0f, 2); @@ -61,12 +63,56 @@ namespace l::nodegraph { int32_t mUnixtimePrev = 0; + // Heikin ashi vars float mOpenPrev = 0.0f; float mClosePrev = 0.0f; - }; + + // Time frame vars + float mOpenMa = 0.0f; + float mCloseMa = 0.0f; + float mHighMa = 0.0f; + float mLowMa = 0.0f; + float mVolMa = 0.0f; + float mQuantMa = 0.0f; + float mBuyVolMa = 0.0f; + float mBuyQuantMa = 0.0f; + }; + /*********************************************************************/ + class TradingDataIOChartInfo : public NodeGraphOp { + public: + TradingDataIOChartInfo(NodeGraphBase* node) : + NodeGraphOp(node, "Chart Info") + { + AddInput2("Symbol", 16, InputFlags(false, true, false, true)); + AddInput2("Base", 16, InputFlags(false, true, false, true)); + AddInput("Index", 2.0f, 1, 0.0f, 10.0f); + AddInput2("Now"); + AddInput2("PTick"); + AddInput2("QStep"); + AddInput2("Price"); + + AddOutput2("Symbol", 16, OutputFlags(false, true)); + AddOutput2("Base", 16, OutputFlags(false, true)); + AddOutput("Index#0", 0.0f); + AddOutput("Index#1", 1.0f); + AddOutput("Index#2", 2.0f); + AddOutput("Index#3", 3.0f); + AddOutput("Now"); + AddOutput("Reset"); + AddOutput("PTick"); + AddOutput("QStep"); + AddOutput("Price"); + } + + virtual ~TradingDataIOChartInfo() = default; + + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + protected: + int32_t mReadSamples = 0; + }; } \ No newline at end of file diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingDetector.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingDetector.h index 33385c93..ce602ecd 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingDetector.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingDetector.h @@ -3,12 +3,6 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include @@ -206,6 +200,7 @@ namespace l::nodegraph { { AddInput("In", 0.0f, 1, -l::math::constants::FLTMAX, l::math::constants::FLTMAX, false, false); AddInput("Mean size", 6.0f, 1, 1.0f, 50.0f); + AddInput("Scale", 1.0f, 1, 0.0f, 1.0f); AddOutput("Trend Basic", 0.0f); AddOutput("Trend Mean", 0.0f); @@ -217,6 +212,7 @@ namespace l::nodegraph { virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { auto input1 = inputs.at(0).GetIterator(numSamples); auto numTrendSamples = static_cast(l::math::max2(inputs.at(1).Get(), 1.0f)); + auto reversalScale = l::math::clamp(inputs.at(2).Get(), 0.0f, 1.0f); auto outputTrendBasic = outputs.at(0).GetIterator(numSamples); auto outputTrendMean = outputs.at(1).GetIterator(numSamples); @@ -225,10 +221,10 @@ namespace l::nodegraph { for (int32_t i = 0; i < numSamples; i++) { float in = (*input1++); - + auto scale = l::math::abs(in) * reversalScale + (1.0f - reversalScale); auto trendBasic = mTrendBasic.process(in); auto trendMean = mTrendMean.process(in, numTrendSamples); - auto reversal = mReversal.process(in); + auto reversal = mReversal.process(in) * scale; auto acceleration = mAcceleration.process(in); *outputTrendBasic++ = trendBasic; diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingFilter.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingFilter.h index 8b9db4ba..22adb3cc 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingFilter.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingFilter.h @@ -3,12 +3,6 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include @@ -29,8 +23,8 @@ namespace l::nodegraph { NodeGraphOp(node, "Flip Gate") { AddInput("In", 0.0f); - AddInput("Pos Max Hold", 0.0f, 1, 0.0f, l::math::constants::FLTMAX); - AddInput("Neg Max Hold", 0.0f, 1, 0.0f, l::math::constants::FLTMAX); + AddInput("Sustain Pos", 0.0f, 1, 0.0f, l::math::constants::FLTMAX); + AddInput("Sustain Neg", 0.0f, 1, 0.0f, l::math::constants::FLTMAX); AddOutput("Gate Hold", 0.0f); AddOutput("Gate", 0.0f); } @@ -77,8 +71,8 @@ namespace l::nodegraph { { AddInput("In"); AddInput("Weight", 1.0f); - AddInput("Kernel Size", 1.0f, 1, 1.0f, 5000.0f); - AddInput("Weight Accent", 1.0f, 1, 0.0f, 100.0f); + AddInput("Kernel", 1.0f, 1, 1.0f, 5000.0f); + AddInput("Accent", 1.0f, 1, 0.0f, 100.0f); AddOutput("Out", 0.0f); } diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingIndicator.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingIndicator.h index 6368c640..b6a359f8 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingIndicator.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpTradingIndicator.h @@ -3,12 +3,6 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include @@ -268,24 +262,45 @@ namespace l::nodegraph { class TradingIndicatorATR : public NodeGraphOp { public: TradingIndicatorATR(NodeGraphBase* node) : - NodeGraphOp(node, "ATR (average true range)") + NodeGraphOp(node, "Average True Range") { - AddInput("In", 0.0f, 1, -l::math::constants::FLTMAX, l::math::constants::FLTMAX, false, false); + AddInput2("Close"); + AddInput2("High"); + AddInput2("Low"); + AddInput("N", 14.0f, 1, 1.0f, 200.0f); + + AddOutput("TR", 0.0f); AddOutput("ATR", 0.0f); } virtual ~TradingIndicatorATR() = default; virtual void Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) override { - auto input = inputs.at(0).GetIterator(numSamples); - auto output = outputs.at(0).GetIterator(numSamples); + auto closeInput = &inputs.at(0).Get(numSamples); + auto highInput = &inputs.at(1).Get(numSamples); + auto lowInput = &inputs.at(2).Get(numSamples); + auto periodN = l::math::max2(1.0f, inputs.at(3).Get()); + + auto trOutput = &outputs.at(0).Get(numSamples); + auto atrOutput = &outputs.at(1).Get(numSamples); for (int32_t i = 0; i < numSamples; i++) { - float in = *input++; - *output++ = in; + float close = *closeInput++; + float high = *highInput++; + float low = *lowInput++; + + auto trueRange = l::math::max3(high - low, l::math::abs(high) - mClosePrev, l::math::abs(low) - mClosePrev); + auto atr = (mATRPrev * (periodN - 1.0f) + trueRange) / periodN; + *trOutput++ = trueRange; + *atrOutput++ = atr; + + mClosePrev = close; + mATRPrev = atr; } } protected: + float mClosePrev = 0.0f; + float mATRPrev = 0.0f; }; /*********************************************************************/ diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpUI.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpUI.h index 2f740bfb..27d5de93 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpUI.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpUI.h @@ -3,12 +3,6 @@ #include "logging/LoggingAll.h" -#include "hid/KeyboardPiano.h" -#include "hid/Midi.h" - -#include "audio/PortAudio.h" -#include "audio/AudioUtils.h" - #include "math/MathFunc.h" #include @@ -19,6 +13,7 @@ #include #include #include +#include namespace l::nodegraph { @@ -47,7 +42,7 @@ namespace l::nodegraph { GraphUISlider(NodeGraphBase* node) : NodeGraphOp(node, "UI Slider") { - AddInput("In", 0.0f); + AddInput2("In", 1, InputFlags(false, false, false, false)); AddInput("Min", 0.0f); AddInput("Max", 1.0f); AddInput("Power", 1.0f); @@ -74,7 +69,7 @@ namespace l::nodegraph { GraphUIText(NodeGraphBase* node) : NodeGraphOp(node, "UI Text") { - AddInput2("In", 1, InputFlags(false, false, false, true)); + AddInput2("In", 1, InputFlags(false, true, true, true)); } virtual ~GraphUIText() = default; @@ -89,21 +84,78 @@ namespace l::nodegraph { class GraphUIChartLine : public NodeGraphOpCached { public: GraphUIChartLine(NodeGraphBase* node) : - NodeGraphOpCached(node, "Chart Lines") + NodeGraphOpCached(node, "Chart Line 1") { - AddInput2("x", 1, InputFlags(false, false, false, false)); - AddInput2("y", 1, InputFlags(false, false, false, false)); - AddInput2("name", 1, InputFlags(false, false, false, true)); - AddOutput("Interleaved Data"); + + AddInput2("X", 1, InputFlags(false, false, false, false)); + AddInput2("Y", 1, InputFlags(false, false, false, false)); + AddInput2("Name", 1, InputFlags(false, true, true, true)); + AddInput("Chart ID", 0.0f, 1, 0.0f, 5.0f); // 0=main, 1=volume, 2=flow hist, 3=flow graph, 4=node chart 1, 5=node chart 2 + + AddOutput("Data"); } - virtual ~GraphUIChartLine() { + virtual ~GraphUIChartLine() = default; + virtual void DefaultDataInit() override { + NodeGraphOp::DefaultDataInit(); + mNode->SetInput(2, "Chart Line"); + } + virtual void ProcessWriteCached(int32_t writtenSamples, int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + int32_t GetChartId() const { return mChartId; } + protected: + int32_t mChartId = 0; + int32_t mLatestUnixtime = 0; + }; + + /*********************************************************************/ + class GraphUIChartLine2 : public NodeGraphOpCached { + public: + GraphUIChartLine2(NodeGraphBase* node) : + NodeGraphOpCached(node, "Chart Line 2") + { + AddInput2("X", 1, InputFlags(false, false, false, false)); + AddInput2("Y1", 1, InputFlags(false, false, false, false)); + AddInput2("Y2", 1, InputFlags(false, false, false, false)); + AddInput2("Name", 1, InputFlags(false, true, true, true)); + AddInput("Chart ID", 0.0f, 1, 0.0f, 5.0f); // 0=main, 1=volume, 2=flow hist, 3=flow graph, 4=node chart 1, 5=node chart 2 + AddOutput("Data"); } + virtual ~GraphUIChartLine2() = default; virtual void DefaultDataInit() override { - mNode->SetInput(2, "Chart Lines"); + NodeGraphOp::DefaultDataInit(); + mNode->SetInput(3, "Chart Line"); } virtual void ProcessWriteCached(int32_t writtenSamples, int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + int32_t GetChartId() const { return mChartId; } protected: + int32_t mChartId = 0; + int32_t mLatestUnixtime = 0; + }; + + /*********************************************************************/ + class GraphUIChartLine3 : public NodeGraphOpCached { + public: + GraphUIChartLine3(NodeGraphBase* node) : + NodeGraphOpCached(node, "Chart Line 3") + { + AddInput2("X", 1, InputFlags(false, false, false, false)); + AddInput2("Y1", 1, InputFlags(false, false, false, false)); + AddInput2("Y2", 1, InputFlags(false, false, false, false)); + AddInput2("Y3", 1, InputFlags(false, false, false, false)); + AddInput2("Name", 1, InputFlags(false, true, true, true)); + AddInput("Chart ID", 0.0f, 1, 0.0f, 5.0f); // 0=main, 1=volume, 2=flow hist, 3=flow graph, 4=node chart 1, 5=node chart 2 + + AddOutput("Data"); + } + virtual ~GraphUIChartLine3() = default; + virtual void DefaultDataInit() override { + NodeGraphOp::DefaultDataInit(); + mNode->SetInput(4, "Chart Line"); + } + virtual void ProcessWriteCached(int32_t writtenSamples, int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + int32_t GetChartId() const { return mChartId; } + protected: + int32_t mChartId = 0; int32_t mLatestUnixtime = 0; }; @@ -113,22 +165,132 @@ namespace l::nodegraph { GraphUICandleSticks(NodeGraphBase* node) : NodeGraphOpCached(node, "Candle Sticks") { - AddInput2("unixtime", 1, InputFlags(false, false, false, false)); - AddInput2("open", 1, InputFlags(false, false, false, false)); - AddInput2("close", 1, InputFlags(false, false, false, false)); - AddInput2("high", 1, InputFlags(false, false, false, false)); - AddInput2("low", 1, InputFlags(false, false, false, false)); - AddInput2("volume", 1, InputFlags(false, false, false, false)); - AddInput2("name", 1, InputFlags(false, false, false, true)); - AddOutput("Interleaved Data"); + AddInput2("Time", 1, InputFlags(false, false, false, false)); + AddInput2("Open", 1, InputFlags(false, false, false, false)); + AddInput2("Close", 1, InputFlags(false, false, false, false)); + AddInput2("High", 1, InputFlags(false, false, false, false)); + AddInput2("Low", 1, InputFlags(false, false, false, false)); + AddInput2("Volume", 1, InputFlags(false, false, false, false)); + AddInput2("Name", 1, InputFlags(false, true, true, true)); + AddInput("Chart ID", 0.0f, 1, 0.0f, 5.0f); // 0=main, 1=volume, 2=flow hist, 3=flow graph, 4=node chart 1, 5=node chart 2 + + AddOutput("Data"); } virtual ~GraphUICandleSticks() = default; virtual void DefaultDataInit() override { + NodeGraphOp::DefaultDataInit(); mNode->SetInput(6, "Candle Sticks"); } void ProcessWriteCached(int32_t writtenSamples, int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + int32_t GetChartId() const { return mChartId; } protected: + int32_t mChartId = 0; int32_t mLatestUnixtime = 0; }; + + /*********************************************************************/ + struct TradePosition { + float mEntry = 0.0f; + float mExit = 0.0f; + float mLotShare = 1.0f; + int32_t mEntryTime = 0; + int32_t mExitTime = 0; + + void Reset(float lotShare = 1.0f) { + mLotShare = lotShare; + mEntry = 0.0f; + mExit = 0.0f; + mEntryTime = 0; + mExitTime = 0; + } + + bool IsReady() { + return mEntryTime == 0 && mExitTime == 0 && mEntry == 0.0f && mExit == 0.0f; + } + + bool HasPosition() { + return mEntryTime > 0 && mExitTime == 0 && mEntry > 0.0f && mExit == 0.0f; + } + + bool HasEntry() { + return mEntryTime > 0 && mEntry > 0.0f; + } + + bool HasExit() { + return mExitTime > 0 && mExit > 0.0f; + } + + bool HasCompleted() { + return mEntryTime > 0 && mExitTime > 0 && mEntry > 0.0f && mExit > 0.0f; + } + + bool TradeEntered(int32_t time) { + return time > 0 && time == mEntryTime && mEntry > 0.0f; + } + + bool TradeExited(int32_t time) { + return time > 0 && time == mExitTime && mExit > 0.0f; + } + + float GetProfit(float slip) { + if (mEntry > 0.0f && mExit > 0.0f) { + auto entry = mEntry * (1.0f + slip); // entry commission + auto change = mExit / entry; + change = change * (1.0f - slip); // exit commission + change = 1.0f + (change - 1.0f) * mLotShare; + return change; + } + return 1.0f; + } + + void Update(float state, float price, int32_t time) { + if (time > 0 && mEntryTime == 0 && state > 0.0f) { + mEntry = price; + mEntryTime = time; + } + if (time > 0 && mEntryTime > 0 && mEntryTime < time && mExitTime == 0 && state < 0.0f) { + mExit = price; + mExitTime = time; + } + } + }; + + class GraphUIChartMarkers : public NodeGraphOp { + public: + GraphUIChartMarkers(NodeGraphBase* node) : + NodeGraphOp(node, "Chart Markers") + { + AddInput2("Time"); + AddInput2("Open"); + AddInput2("Close"); + AddInput2("Entry 1"); + AddInput2("Entry 2"); + AddInput("Slip", 0.0002f, 1, 0.0f, 1.0f); + AddInput2("Name", 1, InputFlags(false, true, true, true)); + AddInput("Pin Length", 30.0f, 1, 1.0f, 200.0f); + AddInput("Pin Size", 5.0f, 1, 1.0f, 40.0f); + AddInput("Font Size", 10.8f, 1, 3.0f, 20.0f); + AddInput("Main Size", 0.5f, 1, 0.0f, 1.0f); + AddInput2("Entry 3"); + } + virtual ~GraphUIChartMarkers() = default; + virtual void DefaultDataInit() override { + NodeGraphOp::DefaultDataInit(); + mNode->SetInput(6, "Chart Markers"); + } + virtual void Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) override; + const std::vector>& GetMarkers() { + return mMarkers; + } + protected: + int32_t mReadSamples = 0; + float mTotalProfit = 1.0f; + + TradePosition mEntry1; + TradePosition mEntry2; + TradePosition mEntry3; + + std::vector> mMarkers; + }; } diff --git a/packages/nodegraph/source/common/NodeGraphSchema.cpp b/packages/nodegraph/source/common/NodeGraphSchema.cpp index 0fae8a85..24324fbd 100644 --- a/packages/nodegraph/source/common/NodeGraphSchema.cpp +++ b/packages/nodegraph/source/common/NodeGraphSchema.cpp @@ -17,18 +17,18 @@ namespace l::nodegraph { } // Insert a path like "a.b.c" - void insertPath(TreeMenuNode& root, std::string_view path, std::string_view name, int32_t nodeId) { + void insertPath(TreeMenuNode& root, std::string_view path, std::string_view name, int32_t nodeId, std::string_view description) { TreeMenuNode* current = &root; for (auto part : l::string::split(path, ".")) { current = findOrCreateChild(*current, part); } - current->mChildren.emplace_back("", name, nodeId); + current->mChildren.emplace_back("", name, nodeId, description); } bool NodeGraphSchema::NodeGraphNewNode(int32_t typeId, int32_t nodeId) { auto id = NewNode(typeId, nodeId); if (id != nodeId) { - LOG(LogError) << "Failed to create node"; + LLOG(LogError) << "Failed to create node " << nodeId << " with type " << typeId << " in schema " << mFullPath; return false; } return true; @@ -38,7 +38,9 @@ namespace l::nodegraph { auto srcNode = GetNode(srcId); auto dstNode = GetNode(dstId); if (srcNode && dstNode && !dstNode->SetInput(dstChannel, *srcNode, srcChannel)) { - LOG(LogError) << "Failed to wire nodes"; + auto srctype = srcNode->GetTypeId(); + auto dsttype = dstNode->GetTypeId(); + LLOG(LogError) << "Failed to wire [type,id,channel] [" << srctype << ":" << srcId << ":" << static_cast(srcChannel) << "] to [" << dsttype << ":" << dstId << ":" << static_cast(dstChannel) << "] in schema " << mFullPath; return false; } return true; @@ -59,7 +61,7 @@ namespace l::nodegraph { bool NodeGraphSchema::Load(std::filesystem::path file) { if (!file.has_filename() || !std::filesystem::exists(file)) { - LOG(LogError) << "Failed to load schema: the file does not exist"; + LLOG(LogError) << "Failed to load schema: the file does not exist"; return false; } @@ -87,12 +89,12 @@ namespace l::nodegraph { bool NodeGraphSchema::Save(std::filesystem::path file, bool cloneOnly) { if (file.empty()) { - LOG(LogError) << "Failed to save schema: there is no file name or path"; + LLOG(LogError) << "Failed to save schema: there is no file name or path. In schema " << mFullPath; return false; } if (!file.has_filename()) { - LOG(LogError) << "Failed to save schema: there is no file name"; + LLOG(LogError) << "Failed to save schema: there is no file name. In schema " << mFullPath; return false; } @@ -109,7 +111,7 @@ namespace l::nodegraph { l::filesystem::File dataFile(file); dataFile.modeBinary().modeWriteTrunc(); if (dataFile.open() && dataFile.write(builder.GetStream()) > 0) { - LOG(LogInfo) << "Created " << file; + LLOG(LogInfo) << "Created " << file; return true; } return false; @@ -130,11 +132,11 @@ namespace l::nodegraph { } if (mVersionMajor < kVersionMajor) { - LOG(LogWarning) << "Schema major version mismatch. Performing automatic upgrade but schema should be saved."; + LLOG(LogWarning) << "Schema major version mismatch. Performing automatic upgrade but schema should be saved. In schema " << mFullPath; // Perform upgrade } else if (mVersionMinor < kVersionMinor) { - LOG(LogWarning) << "Schema minor version is of old version. Schema should still work but should be resaved when suitable."; + LLOG(LogWarning) << "Schema minor version is of old version. Schema should still work but should be resaved when suitable. In schema " << mFullPath; } if (nodeGraphSchema.has_key("Name")) { @@ -186,6 +188,7 @@ namespace l::nodegraph { mCustomNodeCreatorListeners.emplace_back(std::move(customCreator)); } +#ifndef HEADLESS_BUILD void NodeGraphSchema::SetKeyState(l::hid::KeyState* keyState) { mKeyState = keyState; } @@ -197,6 +200,7 @@ namespace l::nodegraph { void NodeGraphSchema::SetMidiManager(l::hid::midi::MidiManager* midiManager) { mMidiManager = midiManager; } +#endif int32_t NodeGraphSchema::NewNode(int32_t typeId, int32_t id) { l::nodegraph::NodeGraphBase* node = nullptr; @@ -222,6 +226,9 @@ namespace l::nodegraph { case 5: node = mMainNodeGraph.NewNode(id, NodeType::Default); break; + case 6: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; // Internal output like NG chart or debug view case 20: @@ -275,6 +282,24 @@ namespace l::nodegraph { case 108: node = mMainNodeGraph.NewNode(id, NodeType::Default); break; + case 109: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 110: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 111: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 112: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 113: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 114: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; // Math logical operators case 120: @@ -292,13 +317,43 @@ namespace l::nodegraph { node = mMainNodeGraph.NewNode(id, NodeType::Default); break; case 141: - node = mMainNodeGraph.NewNode(id, NodeType::Default); + node = mMainNodeGraph.NewNode(id, NodeType::Default); break; case 142: - node = mMainNodeGraph.NewNode(id, NodeType::Default); + node = mMainNodeGraph.NewNode(id, NodeType::Default); break; case 143: - node = mMainNodeGraph.NewNode(id, NodeType::Default); + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 144: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 145: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 146: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 147: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 148: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 149: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 150: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 151: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 152: + node = mMainNodeGraph.NewNode(id, NodeType::Default); + break; + case 153: + node = mMainNodeGraph.NewNode(id, NodeType::Default); break; // Trading data io @@ -308,6 +363,9 @@ namespace l::nodegraph { case 201: node = mMainNodeGraph.NewNode(id, NodeType::ExternalInput, 1); break; + case 202: + node = mMainNodeGraph.NewNode(id, NodeType::ExternalInput); + break; // Trading detectors case 220: @@ -418,6 +476,7 @@ namespace l::nodegraph { break; +#ifndef HEADLESS_BUILD // DeviceIO (midi, keyboard piano) case 400: node = mMainNodeGraph.NewNode(id, NodeType::Default, mKeyState); @@ -455,6 +514,7 @@ namespace l::nodegraph { case 421: node = mMainNodeGraph.NewNode(id, NodeType::ExternalOutput, mAudioOutput); break; +#endif // HEADLESS_BUILD // DataIO input case 500: @@ -480,6 +540,15 @@ namespace l::nodegraph { case 604: node = mMainNodeGraph.NewNode(id, NodeType::ExternalInput); break; + case 605: + node = mMainNodeGraph.NewNode(id, NodeType::ExternalOutput); + break; + case 606: + node = mMainNodeGraph.NewNode(id, NodeType::ExternalOutput); + break; + case 607: + node = mMainNodeGraph.NewNode(id, NodeType::ExternalOutput); + break; @@ -542,9 +611,11 @@ namespace l::nodegraph { mMainNodeGraph.ForEachOutputNode(std::move(cb)); } - void NodeGraphSchema::ForEachNodeType(std::function&)> cb) const { + void NodeGraphSchema::ForEachNodeType(std::string_view search, std::function&)> cb) const { for (auto& it : mRegisteredNodeTypes) { - cb(it.first, it.second); + if (search.empty() || l::string::equal_anywhere(it.first, search)) { + cb(it.first, it.second); + } } } @@ -552,11 +623,15 @@ namespace l::nodegraph { return mPickerRootMenu; } - void NodeGraphSchema::RegisterNodeType(const std::string& typeGroup, int32_t uniqueTypeId, std::string_view typeName) { + void NodeGraphSchema::RegisterNodeType(const std::string& typeGroup, int32_t uniqueTypeId, std::string_view typeName, std::string_view description) { if (!HasNodeType(typeGroup, uniqueTypeId)) { - mRegisteredNodeTypes[typeGroup].push_back(UINodeDesc{ uniqueTypeId, std::string(typeName) }); + UINodeDesc nodeInfo; + nodeInfo.mId = uniqueTypeId; + nodeInfo.mName = typeName; + nodeInfo.mDescription = description; + mRegisteredNodeTypes[typeGroup].push_back(nodeInfo); } - insertPath(mPickerRootMenu, typeGroup, typeName, uniqueTypeId); + insertPath(mPickerRootMenu, typeGroup, typeName, uniqueTypeId, description); } void NodeGraphSchema::RegisterAllOf(const std::string& typeGroup) { @@ -567,6 +642,7 @@ namespace l::nodegraph { RegisterNodeType("Node Graph.Source", 3, "Value [-inf,inf]"); RegisterNodeType("Node Graph.Source", 4, "Time"); RegisterNodeType("Node Graph.Source", 5, "Text"); + RegisterNodeType("Node Graph.Source", 6, "Constants"); } else if (typeGroup == "Node Graph.Output") { RegisterNodeType("Node Graph.Output", 20, "Debug"); @@ -585,9 +661,15 @@ namespace l::nodegraph { RegisterNodeType("Math.Aritmethic", 103, "Neg"); RegisterNodeType("Math.Aritmethic", 104, "Abs"); RegisterNodeType("Math.Aritmethic", 105, "Log"); - RegisterNodeType("Math.Aritmethic", 106, "Mul3"); + RegisterNodeType("Math.Aritmethic", 106, "Mul 3"); RegisterNodeType("Math.Aritmethic", 107, "Madd"); RegisterNodeType("Math.Aritmethic", 108, "Round"); + RegisterNodeType("Math.Aritmethic", 109, "Pow"); + RegisterNodeType("Math.Aritmethic", 110, "Sum 3"); + RegisterNodeType("Math.Aritmethic", 111, "Sum 5"); + RegisterNodeType("Math.Aritmethic", 112, "Minmax 1", "Compares input with a min and a max value. Outputs values in the 1) min/max range, 2) larger or equal to min and 3) less or equal to max, respectively."); + RegisterNodeType("Math.Aritmethic", 113, "Minmax 2", "Compares the inputs. Outputs the 1) smaller and the 2) larger values respectively."); + RegisterNodeType("Math.Aritmethic", 114, "Div", "Divides input 1 with input 2"); } else if (typeGroup == "Math.Logic") { RegisterNodeType("Math.Logic", 120, "And"); @@ -595,14 +677,25 @@ namespace l::nodegraph { RegisterNodeType("Math.Logic", 122, "Xor"); } else if (typeGroup == "Math.Numerical") { - RegisterNodeType("Math.Numerical", 140, "Integral"); - RegisterNodeType("Math.Numerical", 141, "Derivate"); - RegisterNodeType("Math.Numerical", 142, "Difference Normalized"); - RegisterNodeType("Math.Numerical", 143, "Difference"); + RegisterNodeType("Math.Numerical", 140, "Integral", "Basically a temporal summation node with a EWA on the output with a cooefficient 'friction'"); + RegisterNodeType("Math.Numerical", 141, "Change 1", "Temporal change 1. Computes the value: (v_now - v_prev) / (abs(v_now) + abs(v_prev))."); + RegisterNodeType("Math.Numerical", 142, "Difference 2", "Temporal difference 2. Computes the value: (v_now / v_prev - 1)"); + RegisterNodeType("Math.Numerical", 143, "Difference 1", "Temporal difference 1. Computes the value: (v_now - v_prev)."); + RegisterNodeType("Math.Numerical", 144, "Level Trigger", "Determines where some input is located between two extremes (min/max) in the format [0,1] "); + RegisterNodeType("Math.Numerical", 145, "Minmax Channel", "Computes the range between the EWA smootherd min/max inputs"); + RegisterNodeType("Math.Numerical", 146, "Reconstructor 1", "Deconstructs the input into derivatives (change per index) and outputs the sum of through a ewa with a cooefficient of 'friction' {x1 = x0 + friction * (target - x0)}. An second output is provided which is the average of the last two outputs of that function."); + RegisterNodeType("Math.Numerical", 147, "Reconstructor 2", ""); + RegisterNodeType("Math.Numerical", 148, "Unitmap", "Maps the input to [-1,1] via a sigmoid function. A scale factor can be provided that changes the shape of the mapping"); + RegisterNodeType("Math.Numerical", 149, "EMA", "Exponential moving average [ema1=(ema0*(n-1)+input)/n]"); + RegisterNodeType("Math.Numerical", 150, "Change 2", "Temporal change 2. Computes the value: (v_now - v_prev) / abs(v_now)."); + RegisterNodeType("Math.Numerical", 151, "Mean Regression", "Computes the convolution of the exponential distances and can be used as a square root of the variance for computing the mean regression or the trend/direction of the input values."); + RegisterNodeType("Math.Numerical", 152, "Standard Deviation", "Computes the standard deviation and variance."); + RegisterNodeType("Math.Numerical", 153, "SMA", "Simple moving average"); } else if (typeGroup == "Trading.Data IO") { RegisterNodeType("Trading.Data IO", 200, "OCHLV Data In"); RegisterNodeType("Trading.Data IO", 201, "Heikin-Ashi Data In"); + RegisterNodeType("Trading.Data IO", 202, "Chart Info"); } else if (typeGroup == "Trading.Detector") { RegisterNodeType("Trading.Detector", 220, "Trend"); @@ -619,7 +712,7 @@ namespace l::nodegraph { RegisterNodeType("Trading.Indicator", 262, "On-Balance Volume 2 (OBV2)"); RegisterNodeType("Trading.Indicator", 263, "Gated Accumulation (GA)"); //RegisterNodeType("Trading.Indicator", 264, "Volume Relative Strength Index (VRSI)"); - //RegisterNodeType("Trading.Indicator", 265, "Average True Range (ATR)"); + RegisterNodeType("Trading.Indicator", 265, "Average True Range (ATR)"); } else if (typeGroup == "Signal.Generator") { RegisterNodeType("Signal.Generator", 300, "Sine"); @@ -670,12 +763,15 @@ namespace l::nodegraph { else if (typeGroup == "UI") { RegisterNodeType("UI", 600, "UI Checkbox"); RegisterNodeType("UI", 601, "UI Slider"); - RegisterNodeType("UI", 602, "UI Chart Lines"); + RegisterNodeType("UI", 602, "UI Chart Lines 1"); RegisterNodeType("UI", 603, "UI Candle Sticks"); RegisterNodeType("UI", 604, "UI Text"); + RegisterNodeType("UI", 605, "UI Chart Markers"); + RegisterNodeType("UI", 606, "UI Chart Lines 2"); + RegisterNodeType("UI", 607, "UI Chart Lines 3"); } else { - LOG(LogWarning) << "Type group does not exist: " << typeGroup; + LLOG(LogWarning) << "Type group does not exist: " << typeGroup; } } diff --git a/packages/nodegraph/source/common/core/NodeGraphBase.cpp b/packages/nodegraph/source/common/core/NodeGraphBase.cpp index e33707a5..bac557b6 100644 --- a/packages/nodegraph/source/common/core/NodeGraphBase.cpp +++ b/packages/nodegraph/source/common/core/NodeGraphBase.cpp @@ -79,10 +79,22 @@ namespace l::nodegraph { mLastTickCount = tickCount; } + void NodeGraphBase::SetOutputText(int8_t outputChannel, std::string_view text) { + mOutputs.at(outputChannel).SetText(text); + } + + void NodeGraphBase::SetOutput(int8_t outputChannel, float value) { + mOutputs.at(outputChannel).Get() = value; + } + float& NodeGraphBase::GetInput(int8_t inputChannel, int32_t minSize, int32_t offset) { return mInputs.at(inputChannel).Get(minSize, offset); } + std::optional> NodeGraphBase::GetInputBuffer(int8_t inputChannel) { + return mInputs.at(inputChannel).GetBuffer(); + } + std::string_view NodeGraphBase::GetInputText(int8_t inputChannel, int32_t minSize) { return mInputs.at(inputChannel).GetText(minSize); } @@ -91,6 +103,10 @@ namespace l::nodegraph { return mOutputs.at(outputChannel).Get(minSize, offset); } + std::optional> NodeGraphBase::GetOutputBuffer(int8_t outputChannel) { + return mOutputs.at(outputChannel).GetBuffer(); + } + std::string_view NodeGraphBase::GetOutputText(int8_t outputChannel, int32_t minSize) { return mOutputs.at(outputChannel).GetText(minSize); } @@ -112,7 +128,7 @@ namespace l::nodegraph { } bool NodeGraphBase::ClearInput(int8_t inputChannel) { - ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); + //ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); if (!IsValidInOutNum(inputChannel, mInputs.size())) { return false; } @@ -128,7 +144,7 @@ namespace l::nodegraph { } bool NodeGraphBase::SetInput(int8_t inputChannel, NodeGraphBase& source, int8_t sourceOutputChannel) { - ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); + //ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); if (!IsValidInOutNum(inputChannel, mInputs.size())) { return false; } @@ -140,7 +156,7 @@ namespace l::nodegraph { } bool NodeGraphBase::SetInput(int8_t inputChannel, NodeGraphGroup& source, int8_t sourceChannel) { - ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); + //ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); if (!IsValidInOutNum(inputChannel, mInputs.size())) { return false; } @@ -159,7 +175,7 @@ namespace l::nodegraph { } bool NodeGraphBase::SetInput(int8_t inputChannel, float initialValue, int32_t minSize) { - ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); + //ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); if (!IsValidInOutNum(inputChannel, mInputs.size())) { return false; } @@ -176,7 +192,7 @@ namespace l::nodegraph { } bool NodeGraphBase::SetInput(int8_t inputChannel, float* floatPtr) { - ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); + //ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); if (!IsValidInOutNum(inputChannel, mInputs.size())) { return false; } @@ -186,7 +202,7 @@ namespace l::nodegraph { } bool NodeGraphBase::SetInput(int8_t inputChannel, std::string_view text) { - ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); + //ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); if (!IsValidInOutNum(inputChannel, mInputs.size())) { return false; } @@ -205,7 +221,7 @@ namespace l::nodegraph { } bool NodeGraphBase::SetInputBound(int8_t inputChannel, InputBound bound, float boundMin, float boundMax) { - ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); + //ASSERT(inputChannel >= 0 && static_cast(inputChannel) < mInputs.size()); if (!IsValidInOutNum(inputChannel, mInputs.size())) { return false; } diff --git a/packages/nodegraph/source/common/core/NodeGraphGroup.cpp b/packages/nodegraph/source/common/core/NodeGraphGroup.cpp index ca67ea84..85a77500 100644 --- a/packages/nodegraph/source/common/core/NodeGraphGroup.cpp +++ b/packages/nodegraph/source/common/core/NodeGraphGroup.cpp @@ -50,7 +50,7 @@ namespace l::nodegraph { auto it = nodes.as_array(); for (; it.has_next();) { auto e = it.next(); - //LOG(LogInfo) << e.as_dbg_string(); + //LLOG(LogInfo) << e.as_dbg_string(); if (e.has_key("TypeId") && e.has_key("NodeId")) { auto typeId = e.get("TypeId").as_int32(); auto nodeId = e.get("NodeId").as_int32(); @@ -62,15 +62,15 @@ namespace l::nodegraph { //auto name = e.get("Name").as_string(); //auto typeName = e.get("TypeName").as_string(); - mNodeFactory->NodeGraphNewNode(typeId, nodeId); - - if (e.has_key("x") && e.has_key("y")) { - GetNode(nodeId)->GetUIData().x = e.get("x").as_float(); - GetNode(nodeId)->GetUIData().y = e.get("y").as_float(); - } - if (e.has_key("w") && e.has_key("w")) { - GetNode(nodeId)->GetUIData().w = e.get("w").as_float(); - GetNode(nodeId)->GetUIData().h = e.get("h").as_float(); + if (mNodeFactory->NodeGraphNewNode(typeId, nodeId)) { + if (e.has_key("x") && e.has_key("y")) { + GetNode(nodeId)->GetUIData().x = e.get("x").as_float(); + GetNode(nodeId)->GetUIData().y = e.get("y").as_float(); + } + if (e.has_key("w") && e.has_key("w")) { + GetNode(nodeId)->GetUIData().w = e.get("w").as_float(); + GetNode(nodeId)->GetUIData().h = e.get("h").as_float(); + } } } } @@ -82,11 +82,11 @@ namespace l::nodegraph { auto it = nodeData.as_array(); for (; it.has_next();) { auto e = it.next(); - //LOG(LogInfo) << e.as_dbg_string(); + //LLOG(LogInfo) << e.as_dbg_string(); if (e.has_key("NodeId")) { auto nodeId = e.get("NodeId").as_int32(); auto inputInfo = e.get("InputInfo"); - //LOG(LogInfo) << inputInfo.as_dbg_string(); + //LLOG(LogInfo) << inputInfo.as_dbg_string(); auto node = GetNode(nodeId); ASSERT(node); @@ -100,13 +100,13 @@ namespace l::nodegraph { if (data.has_key("Value")) { auto value = data.get("Value").as_float(); if (!node->SetInput(channel, value)) { - LOG(LogError) << "Failed to set channel constant data"; + LLOG(LogError) << "Failed to set channel constant data"; } } else if (data.has_key("Text")) { auto text= data.get("Text").as_string(); if (!node->SetInput(channel, text)) { - LOG(LogError) << "Failed to set channel text data"; + LLOG(LogError) << "Failed to set channel text data"; } } else if (data.has_key("SrcNodeId") && data.has_key("SrcChannel")) { @@ -361,4 +361,10 @@ namespace l::nodegraph { } mLastTickCount = tickCount; } + + void NodeGraphGroup::SendEvent(int32_t id, int32_t cmd, void* userdata) { + for (auto& node : mNodes) { + node->RecieveEvent(id, cmd, userdata); + } + } } \ No newline at end of file diff --git a/packages/nodegraph/source/common/core/NodeGraphInput.cpp b/packages/nodegraph/source/common/core/NodeGraphInput.cpp index d7bea70f..9a4d7b7f 100644 --- a/packages/nodegraph/source/common/core/NodeGraphInput.cpp +++ b/packages/nodegraph/source/common/core/NodeGraphInput.cpp @@ -90,7 +90,7 @@ namespace l::nodegraph { void NodeGraphInput::MinimizeBuffer(int32_t size) { if (mInputType == InputType::INPUT_NODE) { if (mInput.mInputNode != nullptr) { - ASSERT(mInput.mInputNode->GetOutputSize(mInputFromOutputChannel) == size); + //ASSERT(mInput.mInputNode->GetOutputSize(mInputFromOutputChannel) == size); //mInput.mInputNode->GetOutputOf(mInputFromOutputChannel).MinimizeBuffer(size); } } @@ -124,6 +124,24 @@ namespace l::nodegraph { return mInput.mInputFloatConstant; } + std::optional> NodeGraphInput::GetBuffer() { + switch (mInputType) { + case InputType::INPUT_NODE: + if (mInput.mInputNode != nullptr) { + return mInput.mInputNode->GetOutputBuffer(mInputFromOutputChannel); + } + break; + case InputType::INPUT_ARRAY: + if (mInput.mInputFloatBuf != nullptr && !mInput.mInputFloatBuf->empty()) { + return *mInput.mInputFloatBuf; + } + break; + default: + return std::nullopt; + } + return std::nullopt; + } + float& NodeGraphInput::GetArray(int32_t minSize, int32_t offset) { if (mInputType == InputType::INPUT_ARRAY) { if (!mInput.mInputFloatBuf) { diff --git a/packages/nodegraph/source/common/core/NodeGraphOutput.cpp b/packages/nodegraph/source/common/core/NodeGraphOutput.cpp index cc56860f..2475b4b6 100644 --- a/packages/nodegraph/source/common/core/NodeGraphOutput.cpp +++ b/packages/nodegraph/source/common/core/NodeGraphOutput.cpp @@ -57,6 +57,13 @@ namespace l::nodegraph { return std::string_view(out); } + std::optional> NodeGraphOutput::GetBuffer() { + if (mOutputBuf != nullptr && !mOutputBuf->empty()) { + return *mOutputBuf; + } + return std::nullopt; + } + void NodeGraphOutput::SetText(std::string_view text) { auto p = reinterpret_cast(&Get(1 + static_cast(text.size()))); memcpy(p, text.data(), text.size()); diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpDataIO.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpDataIO.cpp index faec935a..676e828c 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpDataIO.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpDataIO.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpDataIO.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpDeviceIOInput.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpDeviceIOInput.cpp index 54393bd8..bf3f95a0 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpDeviceIOInput.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpDeviceIOInput.cpp @@ -1,5 +1,7 @@ #include "nodegraph/operations/NodeGraphOpDeviceIOInput.h" +#ifndef HEADLESS_BUILD + #include "logging/Log.h" #include "audio/AudioUtils.h" #include "hid/Midi.h" @@ -129,7 +131,7 @@ namespace l::nodegraph { } void GraphInputMidiKeyboard::MidiEvent(const l::hid::midi::MidiData& data) { - //LOG(LogInfo) << "listener 1: dev" << data.device << " stat " << data.status << " ch " << data.channel << " d1 " << data.data1 << " d2 " << data.data2; + //LLOG(LogInfo) << "listener 1: dev" << data.device << " stat " << data.status << " ch " << data.channel << " d1 " << data.data1 << " d2 " << data.data2; if (mMidiChannelKeys < 0 || data.channel != static_cast(mMidiChannelKeys)) { return; } @@ -446,5 +448,6 @@ namespace l::nodegraph { } ); } - } + +#endif // HEADLESS_BUILD diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpDeviceIOOutput.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpDeviceIOOutput.cpp index 6c240f5b..c74ce80d 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpDeviceIOOutput.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpDeviceIOOutput.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpDeviceIOOutput.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" @@ -17,6 +16,7 @@ namespace l::nodegraph { inputs.at(2).SetConstant(mValue); } +#ifndef HEADLESS_BUILD /*********************************************************************/ void GraphOutputSpeaker::Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector&) { if (mAudioStream == nullptr) { @@ -84,6 +84,7 @@ namespace l::nodegraph { } ); } +#endif // HEADLESS_BUILD /*********************************************************************/ void GraphOutputPlot::Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) { @@ -112,7 +113,9 @@ namespace l::nodegraph { mTimer = duration / 1000.0f; +#ifndef HEADLESS_BUILD l::audio::PCBeep(freq, duration); +#endif } if (value < 0.5f && mTriggered) { mTriggered = false; diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpMathAritmethic.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpMathAritmethic.cpp index fd97e3d0..d3dcb624 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpMathAritmethic.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpMathAritmethic.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpMathAritmethic.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpMathLogic.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpMathLogic.cpp index f9786148..462d09ed 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpMathLogic.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpMathLogic.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpMathLogic.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpMathNumerical.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpMathNumerical.cpp index 4e22ec34..fc7c2ef2 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpMathNumerical.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpMathNumerical.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpMathNumerical.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" @@ -9,4 +8,577 @@ namespace l::nodegraph { + /*********************************************************************/ + void MathNumericalIntegral::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + auto input0 = &inputs.at(0).Get(numSamples); + auto friction = inputs.at(1).Get(); + auto frictionFactor = l::math::clamp(l::math::pow(friction, 0.25f), 0.0f, 1.0f); + auto output = &outputs.at(0).Get(numSamples); + + if (mReadSamples == 0) { + mOutput = 0.0f; + } + + for (int32_t i = 0; i < numSamples; i++) { + mOutput += *input0++; + mOutput *= frictionFactor; + *output++ = mOutput; + } + + mReadSamples += numSamples; + + if (mReadSamples >= numCacheSamples) { + mReadSamples = 0; + mOutput = 0.0f; + } + + if (isnan(mOutput)) { + mOutput = 0.0f; + } + } + + /*********************************************************************/ + void MathNumericalTemporalChange1::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + auto input0 = &inputs.at(0).Get(numSamples); + auto output = &outputs.at(0).Get(numSamples); + + for (int32_t i = 0; i < numSamples; i++) { + float input = *input0++; + float value = input - mInputPrev; + float divisor = l::math::abs(input) + l::math::abs(mInputPrev); + if (divisor > 0.0f) { + value = 2.0f * value / divisor; + } + else { + value = 0.0f; + } + mInputPrev = input; + *output++ = value; + } + + mReadSamples += numSamples; + + if (mReadSamples >= numCacheSamples) { + mReadSamples = 0; + mInputPrev = 0.0f; + } + } + + /*********************************************************************/ + void MathNumericalTemporalChange2::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + auto input0 = &inputs.at(0).Get(numSamples); + auto output = &outputs.at(0).Get(numSamples); + + for (int32_t i = 0; i < numSamples; i++) { + float input = *input0++; + float value = input - mInputPrev; + float divisor = l::math::abs(input); + if (divisor > 0.0f) { + value = value / divisor; + } + else { + value = 0.0f; + } + mInputPrev = input; + *output++ = value; + } + + mReadSamples += numSamples; + + if (mReadSamples >= numCacheSamples) { + mReadSamples = 0; + mInputPrev = 0.0f; + } + } + + /*********************************************************************/ + void MathNumericalDiff2::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + auto input0 = &inputs.at(0).Get(numSamples); + auto output = &outputs.at(0).Get(numSamples); + + for (int32_t i = 0; i < numSamples; i++) { + float input = *input0++; + float value = mInputPrev; + if (mInputPrev != 0.0f) { + if (input > 0.0f && mInputPrev > 0.0f) { + value = input / mInputPrev; + value = value - 1.0f; + } + else if (input < 0.0f && mInputPrev < 0.0f) { + value = input / mInputPrev; + value = (value - 1.0f); + } + else { + value = 0.0f; + } + } + mInputPrev = input; + *output++ = value; + } + + mReadSamples += numSamples; + + if (mReadSamples >= numCacheSamples) { + mReadSamples = 0; + mInputPrev = 0.0f; + } + } + + /*********************************************************************/ + void MathNumericalDiff1::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + auto input0 = &inputs.at(0).Get(numSamples); + auto output = &outputs.at(0).Get(numSamples); + + for (int32_t i = 0; i < numSamples; i++) { + float input = *input0++; + float value = input - mInputPrev; + mInputPrev = input; + *output++ = value; + } + + mReadSamples += numSamples; + + if (mReadSamples >= numCacheSamples) { + mReadSamples = 0; + mInputPrev = 0.0f; + } + } + + /*********************************************************************/ + void MathNumericalLevelTrigger::Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) { + auto inInput = &inputs.at(0).Get(numSamples); + auto maxInput = inputs.at(1).GetIterator(numSamples); + auto minInput = inputs.at(2).GetIterator(numSamples); + auto numLevels = l::math::clamp(inputs.at(3).Get(), 1.0f, 10.0f); + auto max = l::math::clamp(inputs.at(4).Get(), 0.0f, 3.0f); + auto min = l::math::clamp(inputs.at(5).Get(), -3.0f, max); + auto levelOutput = &outputs.at(0).Get(numSamples); + auto pulseOutput = &outputs.at(1).Get(numSamples); + + for (int32_t i = 0; i < numSamples; i++) { + float in = *inInput++; + float inMax = *maxInput++; + float inMin = *minInput++; + float inRange = inMax - inMin; + float inRangeFactor = 0.0f; + if (inRange > 0.0f) { + inRangeFactor = (in - inMin) / inRange; + } + float minmaxRange = max - min; + float level = 0.0f; + if (minmaxRange > 0.0f) { + float levelMinMax = (inRangeFactor - min) / minmaxRange; + level = levelMinMax; + } + auto levelExpanded = numLevels * level; + + float pulse = 0.0f; + if (level >= 0.0f && level <= 1.0f && static_cast(levelExpanded) > static_cast(mLevelPrev)) { + pulse = 1.0f; + mLevelPrev = levelExpanded; + } + else if (level >= 0.0f && level <= 1.0f && static_cast(levelExpanded) < static_cast(mLevelPrev)) { + pulse = -1.0f; + mLevelPrev = levelExpanded; + } + + *levelOutput++ = level; + *pulseOutput++ = pulse; + } + } + + /*********************************************************************/ + void MathNumericalMinMaxChannel::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + auto inInput = &inputs.at(0).Get(numSamples); + auto upperInput = inputs.at(1).GetIterator(numSamples); + auto lowerInput = inputs.at(2).GetIterator(numSamples); + auto friction = inputs.at(3).Get(); + + auto rangeOutput = &outputs.at(0).Get(numSamples); + auto rangeMaxOutput = &outputs.at(1).Get(numSamples); + auto rangeMinOutput = &outputs.at(2).Get(numSamples); + auto rangeNormOutput = &outputs.at(3).Get(numSamples); + + if (mReadSamples == 0) { + mCurRangeMax = -100000000000000.0f; + mCurRangeMin = 100000000000000.0f; + } + + for (int32_t i = 0; i < numSamples; i++) { + float in = *inInput++; + float upper = *upperInput++; + float lower = *lowerInput++; + + lower = lower > in ? in - 0.0000001f : lower; + upper = upper < in ? in + 0.0000001f : upper; + + auto range = upper - lower; + if (mCurRangeMax < range) { + mCurRangeMax = range; + } + if (mCurRangeMin > range) { + mCurRangeMin = range; + } + + mCurRangeMax += friction * (range - mCurRangeMax); + mCurRangeMin += friction * (range - mCurRangeMin); + + + *rangeOutput++ = range; + *rangeMaxOutput++ = mCurRangeMax; + *rangeMinOutput++ = mCurRangeMin; + + auto rangeDiff = mCurRangeMax - mCurRangeMin; + auto rangeNorm = 0.5f; + if (rangeDiff > 0.0f) { + rangeNorm = (range - mCurRangeMin) / rangeDiff; + } + + *rangeNormOutput++ = rangeNorm; + } + + mReadSamples += numSamples; + + if (mReadSamples >= numCacheSamples) { + mReadSamples = 0; + } + } + + /*********************************************************************/ + void MathNumericalReconstructor1::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + + auto inInput = &inputs.at(0).Get(numSamples); + auto baseInput = inputs.at(1).GetIterator(); + + auto friction1 = inputs.at(2).Get(); + auto friction2 = inputs.at(3).Get(); + auto scale1 = inputs.at(4).Get(); + auto scale2 = inputs.at(5).Get(); + + auto frictionFactor1 = l::math::clamp(l::math::pow(friction1, 0.25f), 0.0f, 1.0f); + auto frictionFactor2 = l::math::clamp(l::math::pow(friction2, 0.25f), 0.0f, 1.0f); + + auto outputDiff = &outputs.at(0).Get(numSamples); + auto outputDiffBase = &outputs.at(1).Get(numSamples); + auto outputIntegral1 = &outputs.at(2).Get(numSamples); + auto outputBase1 = &outputs.at(3).Get(numSamples); + auto outputIntegral2 = &outputs.at(4).Get(numSamples); + auto outputBase2 = &outputs.at(5).Get(numSamples); + + if (mReadSamples == 0) { + mInputPrev = *inInput; + + } + for (int32_t i = 0; i < numSamples; i++) { + float in = *inInput++; + auto base = *baseInput++; + + // derivate + float diff = in - mInputPrev; + mInputPrev = in; + + // integral 1 + mOutput1 += diff; + mOutput1 *= frictionFactor1; + + // integral 2 + mOutput2 += (diff + mDiffPrev) * 0.5f; + mOutput2 *= frictionFactor2; + + mDiffPrev = diff; + + auto outputScaled1 = mOutput1 * scale1; + auto outputScaled2 = mOutput2 * scale2; + + *outputDiff++ = diff; + *outputDiffBase++ = diff + base; + *outputIntegral1++ = mOutput1; + *outputBase1++ = outputScaled1 + base; + *outputIntegral2++ = mOutput2; + *outputBase2++ = outputScaled2 + base; + } + + mReadSamples += numSamples; + + if (mReadSamples >= numCacheSamples) { + mReadSamples = 0; + mOutput1 = 0.0f; + mOutput2 = 0.0f; + mInputPrev = 0.0f; + mDiffPrev = 0.0f; + } + } + + /*********************************************************************/ + void MathNumericalReconstructor2::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + + auto in1Input = &inputs.at(0).Get(numSamples); + auto in2Input = &inputs.at(1).Get(numSamples); + auto friction1 = inputs.at(2).Get(); + auto friction2 = inputs.at(3).Get(); + auto frictionFactor1 = l::math::clamp(l::math::pow(friction1, 0.25f), 0.0f, 1.0f); + auto frictionFactor2 = l::math::clamp(l::math::pow(friction2, 0.25f), 0.0f, 1.0f); + + auto intgr1Output = &outputs.at(0).Get(numSamples); + auto intgr2Output = &outputs.at(1).Get(numSamples); + auto intgrBothOutput = &outputs.at(2).Get(numSamples); + + auto in1Enabled = inputs.at(0).HasInputNode(); + auto in2Enabled = inputs.at(1).HasInputNode(); + + if (mReadSamples == 0) { + mIn1Prev1 = 0.0f; + mIn2Prev1 = 0.0f; + + if (in1Enabled) { + mIn1Prev1 = *in1Input; + } + if (in2Enabled) { + mIn2Prev1 = *in2Input; + } + mInPrev1 = (mIn1Prev1 + mIn2Prev1) * 0.5f; + } + for (int32_t i = 0; i < numSamples; i++) { + float in1 = 0.0f; + float in2 = 0.0f; + + if (in1Enabled) { + in1 = *in1Input++; + } + if (in2Enabled) { + in2 = *in2Input++; + } + + auto in = (in1 + in2) * 0.5f; + + auto in1Diff = in1 - mIn1Prev1; + auto in2Diff = in2 - mIn2Prev1; + auto inDiff = in - mInPrev1; + + mIn1Accum += in1Diff; + mIn1Accum *= frictionFactor2; + mIn2Accum += in2Diff; + mIn2Accum *= frictionFactor2; + mInAccum += inDiff; + mInAccum *= frictionFactor1; + + auto in1Accum = mIn1Accum - mIn1AccumPrev1; + auto in2Accum = mIn2Accum - mIn2AccumPrev1; + auto inAccum = mInAccum - mInAccumPrev1; + + *intgr1Output++ = in1Accum; + *intgr2Output++ = in2Accum; + *intgrBothOutput++ = inAccum; + + mIn1Prev1 = in1; + mIn2Prev1 = in2; + mInPrev1 = in; + + mIn1AccumPrev1 = mIn1Accum; + mIn2AccumPrev1 = mIn2Accum; + mInAccumPrev1 = mInAccum; + } + + mReadSamples += numSamples; + + if (mReadSamples >= numCacheSamples) { + mReadSamples = 0; + } + } + + /*********************************************************************/ + void MathNumericalUnitmap::Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) { + auto inInput = &inputs.at(0).Get(numSamples); + auto k = l::math::pow(inputs.at(1).Get(), 2.0f); + auto offs = inputs.at(2).Get(); + offs = offs * offs * offs; + + auto outOutput = &outputs.at(0).Get(numSamples); + + for (int32_t i = 0; i < numSamples; i++) { + float in = *inInput++; + auto out = l::math::functions::sigmoid(in, k) * 2.0f - 1.0f; + *outOutput++ = offs + out; + } + } + + /*********************************************************************/ + void MathNumericalEMA::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + auto inInput = &inputs.at(0).Get(numSamples); + auto n = l::math::max2(inputs.at(1).Get(), 1.0f); + + auto outOutput = &outputs.at(0).Get(numSamples); + + if (mReadSamples == 0) { + mEmaAccum = *inInput; + } + + for (int32_t i = 0; i < numSamples; i++) { + float in = *inInput++; + mEmaAccum = (mEmaAccum * (n - 1.0f) + in) / n; + *outOutput++ = mEmaAccum; + } + + mReadSamples += numSamples; + if (mReadSamples == numCacheSamples) { + mReadSamples = 0; + } + } + + /*********************************************************************/ + void MathNumericalSMA::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + auto inInput = &inputs.at(0).Get(numSamples); + auto n = l::math::max2(inputs.at(1).Get(), 1.0f); + + auto outOutput = &outputs.at(0).Get(numSamples); + + if (mReadSamples == 0) { + auto len = static_cast(n); + if (mValues.size() != len) { + mValues.resize(len); + } + float in = *inInput; + for (auto& v : mValues) { + v = in; + } + mSum = in * mValues.size(); + } + + auto factor = 1.0f / mValues.size(); + + for (int32_t i = 0; i < numSamples; i++) { + float in = *inInput++; + + mValues.push_back(in); + auto oldestValue = mValues.front(); + mValues.pop_front(); + mSum += in - oldestValue; + auto mean = mSum * factor; + *outOutput++ = mean; + } + + mReadSamples += numSamples; + if (mReadSamples == numCacheSamples) { + mReadSamples = 0; + } + } + /*********************************************************************/ + void MathNumericalMeanExpRegression::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + auto inInput = &inputs.at(0).Get(numSamples); + auto n = static_cast(l::math::max2(inputs.at(1).Get(), 1.0f) + 0.00001f); + auto exp = inputs.at(2).Get(); + auto distribution = inputs.at(3).Get(); + + auto meanOutput = &outputs.at(0).Get(numSamples); + auto meanExpOutput = &outputs.at(1).Get(numSamples); + + if (mReadSamples == 0) { + mValues.clear(); + } + + for (int32_t i = 0; i < numSamples; i++) { + float in = *inInput++; + + if (mValues.size() >= n) { + mValues.erase(mValues.begin()); + } + else { + auto numToAdd = static_cast(n - mValues.size() - 1); + for (int32_t j = 0; j < numToAdd; j++) { + mValues.push_back(in); + } + } + mValues.push_back(in); + + auto mean = 0.0f; + { // calculate the mean value with the distribution in mind + auto count = 0; + auto sumFactor = 0.0f; + for (auto& value : mValues) { + auto distributionFactor = l::math::pow(count / static_cast(n), distribution); + mean += value * distributionFactor; + sumFactor += distributionFactor; + count++; + } + if (sumFactor > 0.0f) { + mean /= sumFactor; + } + } + + auto meanSquareSum = 0.0f; + { // calculate the mean regression with the sum of the exponential distance to the mean, with distribution in mind + auto count = 0; + auto sumFactor = 0.0f; + for (auto& value : mValues) { + auto distributionFactor = l::math::pow(count / static_cast(n), distribution); + meanSquareSum += l::math::pow(l::math::abs(value - mean), exp) * distributionFactor; + sumFactor += distributionFactor; + count++; + } + if (sumFactor > 0.0f) { + meanSquareSum /= sumFactor; + } + } + + *meanOutput++ = mean; + *meanExpOutput++ = meanSquareSum; + } + + mReadSamples += numSamples; + if (mReadSamples == numCacheSamples) { + mReadSamples = 0; + } + } + + /********************************************************************/ + + void MathNumericalStdDev::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + auto inInput = &inputs.at(0).Get(numSamples); + auto period = l::math::max2(inputs.at(1).Get(), 1.0f); + auto sigmaBand = inputs.at(2).Get(1); + + auto ewmaOutput = &outputs.at(0).Get(numSamples); + auto stddevOutput = &outputs.at(1).Get(numSamples); + auto upperOutput = &outputs.at(2).Get(numSamples); + auto lowerOutput = &outputs.at(3).Get(numSamples); + auto zscoreOutput = &outputs.at(4).Get(numSamples); + + if (mReadSamples == 0) { + alpha = 1.0f / period; + ema_prev = *inInput; + variance_ewma = 0.0f; + } + + for (int32_t i = 0; i < numSamples; i++) { + auto in = *inInput++; + + // Deviation from EMA (for population std dev of the error) + auto ema_current = alpha * in + (1.0f - alpha) * ema_prev; + ema_prev = ema_current; + + auto deviation = in - ema_current; + auto deviationSquared = deviation * deviation; + + // Use exponentially weighted moving variance (more responsive) + // We maintain an EWMA of squared deviations + variance_ewma = alpha * deviationSquared + (1.0f - alpha) * variance_ewma; + auto stddev = l::math::sqrt(variance_ewma); + + auto upper_band = ema_current + sigmaBand * stddev; // e.g., 2-sigma band + auto lower_band = ema_current - sigmaBand * stddev; + auto z_score = (stddev > 0.0f) ? deviation / stddev : 0.0f; + + *ewmaOutput++ = ema_current; + *stddevOutput++ = stddev; + *upperOutput++ = upper_band; + *lowerOutput++ = lower_band; + *zscoreOutput++ = z_score; + } + + mReadSamples += numSamples; + if (mReadSamples == numCacheSamples) { + mReadSamples = 0; + } + } + } diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpSignalControl.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpSignalControl.cpp index 329c62ca..bac97630 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpSignalControl.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpSignalControl.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpSignalControl.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" #include "math/MathSmooth.h" diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpSignalEffect.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpSignalEffect.cpp index 0cb0edc7..6e8ee3fe 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpSignalEffect.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpSignalEffect.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpSignalEffect.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" #include "math/MathSmooth.h" diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpSignalFilter.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpSignalFilter.cpp index a251235a..95607e3f 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpSignalFilter.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpSignalFilter.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpSignalFilter.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" @@ -189,27 +188,21 @@ namespace l::nodegraph { float balanceDivisorSum = 0.0f; { // remove a part of the first sample of the sum as it is not part of the moving average auto fac = mFilterWeight[mFilterStateIndex] * l::math::abs(balanceFactor) * widthFrac; - auto sign = l::math::functions::sign(fac); - fac = l::math::pow(fac * sign, gamma); - fac *= sign; + fac = l::math::pow(fac, gamma); outVal += fac * mFilterState[mFilterStateIndex]; balanceDivisorSum += fac; balanceFactor += balanceDelta * widthFrac; } for (int32_t j = mFilterStateIndex + 1; j < bufferSize; j++) { auto fac = mFilterWeight[j] * l::math::abs(balanceFactor); - auto sign = l::math::functions::sign(fac); - fac = l::math::pow(fac * sign, gamma); - fac *= sign; + fac = l::math::pow(fac, gamma); outVal += fac * mFilterState[j]; balanceDivisorSum += fac; balanceFactor += balanceDelta; } for (int32_t j = 0; j < mFilterStateIndex; j++) { auto fac = mFilterWeight[j] * l::math::abs(balanceFactor); - auto sign = l::math::functions::sign(fac); - fac = l::math::pow(fac * sign, gamma); - fac *= sign; + fac = l::math::pow(fac, gamma); outVal += fac * mFilterState[j]; balanceDivisorSum += fac; balanceFactor += balanceDelta; diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpSignalGenerator.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpSignalGenerator.cpp index b6090f72..ddd7b800 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpSignalGenerator.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpSignalGenerator.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpSignalGenerator.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" @@ -120,25 +119,34 @@ namespace l::nodegraph { } /*********************************************************************/ - void SignalGeneratorSine::Process(int32_t numSamples, int32_t, std::vector& inputs, std::vector& outputs) { + void SignalGeneratorSine::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { float* output0 = &outputs.at(0).Get(numSamples); float updateRate = 256.0f; + if (mReadSamples == 0) { + auto reset = inputs.at(5).Get(); + if (reset > 0.0f) { + mVolume = 0.0f; + mVol = 0.0f; + + mPhase = 0.0f; + mPhaseFmod = 0.0f; + mWave = 0.0f; + mSamplesUntilUpdate = 0.0f; + } + } + mSamplesUntilUpdate = l::audio::BatchUpdate(updateRate, mSamplesUntilUpdate, 0, numSamples, [&]() { mFreq = l::math::max2(static_cast(inputs.at(0).Get()), 0.0); mVolume = inputs.at(1).Get(); - mReset = inputs.at(5).Get(); if (mFreq == 0.0f) { mVolume = 0.0f; outputs.at(0).mOutput = 0.0f; return updateRate; } - if (mReset > 0.5f) { - mVolume = 0.0f; - } mDeltaTime = 1.0 / 44100.0; return updateRate; @@ -191,6 +199,11 @@ namespace l::nodegraph { } } ); + + mReadSamples += numSamples; + if (mReadSamples >= numCacheSamples) { + mReadSamples = 0; + } } /*********************************************************************/ diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpSource.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpSource.cpp index 9dcb7fad..cc05dfa1 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpSource.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpSource.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpSource.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" @@ -14,10 +13,19 @@ namespace l::nodegraph { /*********************************************************************/ void GraphSourceConstants::Process(int32_t, int32_t, std::vector& inputs, std::vector& outputs) { auto input0 = inputs.at(0).GetIterator(1); + auto input1 = inputs.at(1).GetIterator(1); + auto input2 = inputs.at(2).GetIterator(1); + auto input3 = inputs.at(3).GetIterator(1); auto output0 = outputs.at(0).GetIterator(1); + auto output1 = outputs.at(1).GetIterator(1); + auto output2 = outputs.at(2).GetIterator(1); + auto output3 = outputs.at(3).GetIterator(1); for (int8_t i = 0; i < mNumOutputs; i++) { *output0 = *input0; + *output1 = *input1; + *output2 = *input2; + *output3 = *input3; } } @@ -82,4 +90,29 @@ namespace l::nodegraph { auto textIn = inputs.at(0).GetText(16); outputs.at(0).SetText(textIn); } + + /*********************************************************************/ + void GraphSourceConstants2::Process(int32_t, int32_t, std::vector& inputs, std::vector& outputs) { + auto input0 = &inputs.at(0).Get(); + auto input1 = &inputs.at(1).Get(); + auto input2 = &inputs.at(2).Get(); + auto input3 = &inputs.at(3).Get(); + auto min = inputs.at(4).Get(); + auto max = inputs.at(5).Get(); + auto output0 = outputs.at(0).GetIterator(1); + auto output1 = outputs.at(1).GetIterator(1); + auto output2 = outputs.at(2).GetIterator(1); + auto output3 = outputs.at(3).GetIterator(1); + + for (int8_t i = 0; i < mNumOutputs; i++) { + *input0 = l::math::clamp(*input0, min, max); + *input1 = l::math::clamp(*input1, min, max); + *input2 = l::math::clamp(*input2, min, max); + *input3 = l::math::clamp(*input3, min, max); + *output0 = *input0; + *output1 = *input1; + *output2 = *input2; + *output3 = *input3; + } + } } diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpTradingDataIO.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpTradingDataIO.cpp index 290962ab..6f67f4bb 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpTradingDataIO.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpTradingDataIO.cpp @@ -1,9 +1,8 @@ #include "nodegraph/operations/NodeGraphOpTradingDataIO.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" -#include "math/MathFunc.h" +#include #include @@ -14,25 +13,23 @@ namespace l::nodegraph { if (mInputHasChanged) { auto symbolInput = inputs.at(1).GetText(16); auto baseInput = inputs.at(2).GetText(16); - auto intervalInput = static_cast(l::math::clamp(inputs.at(3).Get(1), 0.0f, 9.0f)); + auto intervalInput = static_cast(l::math::clamp(inputs.at(3).Get(1), 0.0f, 9.9999f)); outputs.at(0).SetText(symbolInput); outputs.at(1).SetText(baseInput); float* intervalOut = &outputs.at(2).Get(1); - *intervalOut = math::max2(1.0f, static_cast(kIntervals[intervalInput])); + *intervalOut = l::math::max2(1.0f, static_cast(kIntervals[intervalInput])); } } void TradingDataIOOCHLVDataIn::ProcessReadCached(int32_t readSamples, int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { - if (readSamples == 0) { - mUnixtimePrev = 0; - } - int32_t stride = 9; inputs.at(0).MinimizeBuffer(numCacheSamples * stride); auto in = &inputs.at(0).Get(numCacheSamples * stride, readSamples * stride); + auto timeframeMultiplier = l::math::max2(static_cast(inputs.at(4).Get()), 1); + auto friction = inputs.at(5).Get(); float* out1 = &outputs.at(3).Get(numSamples); // unixtime float* out2 = &outputs.at(4).Get(numSamples); // open @@ -48,6 +45,12 @@ namespace l::nodegraph { auto intervalMinutes = static_cast(outputs.at(2).Get(1) + 0.5f); + bool reset = false; + if (mReadSamples == 0) { + reset = true; + mUnixtimePrev = 0; + } + if (mMode == 0) { for (int32_t j = 0; j < numSamples; j++) { auto offset = j * stride; @@ -57,7 +60,7 @@ namespace l::nodegraph { if (mUnixtimePrev == 0) { mUnixtimePrev = unixtime; } - else if (unixtime != mUnixtimePrev + intervalMinutes * 60) { + else if (unixtime == mUnixtimePrev) { unixtime = 0; unixtimef = l::math::algorithm::convert(unixtime); } @@ -65,23 +68,60 @@ namespace l::nodegraph { mUnixtimePrev = unixtime; } - *out1++ = unixtimef; // unixtime auto o = in[offset + 1]; auto c = in[offset + 2]; auto h = in[offset + 3]; auto l = in[offset + 4]; - auto v = in[offset + 5]; + auto v = in[offset + 5]; // total volume + auto q = in[offset + 6]; // total quantity + auto bv = in[offset + 7]; // buy volume + auto bq = in[offset + 8]; // buy quantity + + auto timemin = unixtime / 60; + if (reset || timemin % timeframeMultiplier == 0) { + reset = false; + // time interval looping so reset moving averages + mOpenMa = o; // simply first value + mCloseMa = c; + mHighMa = h; + mLowMa = l; + mVolMa = v; + mQuantMa = q; + mBuyVolMa = bv; + mBuyQuantMa = bq; + } + else { + // last value + mCloseMa = c; + + // smooth + mOpenMa += friction * (mCloseMa - mOpenMa); + mHighMa += friction * (mCloseMa - mHighMa); + mLowMa += friction * (mCloseMa - mLowMa); + + // extremes + mHighMa = l::math::max2(mHighMa, h); + mLowMa = l::math::min2(mLowMa, l); + + // just the sums + mVolMa += v; + mQuantMa += q; + mBuyVolMa += bv; + mBuyQuantMa += bq; + } - *out2++ = o; - *out3++ = c; - *out4++ = h; - *out5++ = l; - *out6++ = v; - *out7++ = in[offset + 6]; // quantity - *out8++ = in[offset + 7]; // buy volume - *out9++ = in[offset + 5] - in[offset + 7]; // sell volume - *out10++ = in[offset + 8]; // buy quantity - *out11++ = in[offset + 6] - in[offset + 8]; // sell quantity + *out1++ = unixtimef; // unixtime + *out2++ = mOpenMa; + *out3++ = mCloseMa; + *out4++ = mHighMa; + *out5++ = mLowMa; + *out6++ = mVolMa; + *out7++ = mQuantMa; // quantity + + *out8++ = mBuyVolMa; // buy volume + *out9++ = mVolMa - mBuyVolMa; // sell volume + *out10++ = mBuyQuantMa; // buy quantity + *out11++ = mQuantMa - mBuyQuantMa; // sell quantity } } else if (mMode == 1) { @@ -101,7 +141,6 @@ namespace l::nodegraph { mUnixtimePrev = unixtime; } - *out1++ = unixtimef; // unixtime auto o = in[offset + 1]; auto c = in[offset + 2]; auto h = in[offset + 3]; @@ -115,6 +154,7 @@ namespace l::nodegraph { mOpenPrev = open; mClosePrev = close; + *out1++ = unixtimef; // unixtime *out2++ = open; *out3++ = close; *out4++ = high; @@ -129,4 +169,44 @@ namespace l::nodegraph { } } + + /*********************************************************************/ + + void TradingDataIOChartInfo::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + auto symbolInput = inputs.at(0).GetText(16); + auto baseInput = inputs.at(1).GetText(16); + auto indexInput = l::math::clamp(inputs.at(2).Get(), 0.0f, 9.9999f); + auto now = inputs.at(3).Get(); + auto ptick = inputs.at(4).Get(); + auto qstep = inputs.at(5).Get(); + auto price = inputs.at(6).Get(); + + outputs.at(0).SetText(symbolInput); + outputs.at(1).SetText(baseInput); + float* indexOut0 = &outputs.at(2).Get(); + float* indexOut1 = &outputs.at(3).Get(); + float* indexOut2 = &outputs.at(4).Get(); + float* indexOut3 = &outputs.at(5).Get(); + float* nowOutput = &outputs.at(6).Get(); + float* resetOutput = &outputs.at(7).Get(); + float* ptickOutput = &outputs.at(8).Get(); + float* qstepOutput = &outputs.at(9).Get(); + float* priceOutput = &outputs.at(10).Get(); + + *indexOut0 = l::math::clamp(indexInput, 0.0f, 9.9999f); + *indexOut1 = l::math::clamp(indexInput + 1.0f, 0.0f, 9.9999f); + *indexOut2 = l::math::clamp(indexInput + 2.0f, 0.0f, 9.9999f); + *indexOut3 = l::math::clamp(indexInput + 3.0f, 0.0f, 9.9999f); + *nowOutput = now; + *resetOutput = mReadSamples == 0; + *ptickOutput = ptick; + *qstepOutput = qstep; + *priceOutput = price; + + mReadSamples += numSamples; + if (mReadSamples >= numCacheSamples) { + mReadSamples = 0; + } + } + } diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpTradingDetector.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpTradingDetector.cpp index 56401ad0..424718db 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpTradingDetector.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpTradingDetector.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpTradingDetector.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpTradingFilter.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpTradingFilter.cpp index 59ce8262..edbfff83 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpTradingFilter.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpTradingFilter.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpTradingFilter.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" @@ -135,7 +134,7 @@ namespace l::nodegraph { float width = l::math::max2(inputs.at(2).Get(), 1.0f); float weightAccent = l::math::clamp(inputs.at(3).Get(), 0.0f, 100.0f); - auto output = outputs.at(0).GetIterator(numSamples); + auto output = &outputs.at(0).Get(numSamples); int32_t widthInt = 1 + static_cast(width); int32_t bufferSize = widthInt; diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpTradingIndicator.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpTradingIndicator.cpp index daac797c..dced1134 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpTradingIndicator.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpTradingIndicator.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpTradingIndicator.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpUI.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpUI.cpp index a54d2495..a17aeed8 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpUI.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpUI.cpp @@ -1,7 +1,6 @@ #include "nodegraph/operations/NodeGraphOpUI.h" #include "logging/Log.h" -#include "audio/AudioUtils.h" #include "math/MathFunc.h" @@ -97,10 +96,14 @@ namespace l::nodegraph { std::string_view GraphUIText::GetOutputText() { return mOutputText.str(); } + /*********************************************************************/ void GraphUIChartLine::ProcessWriteCached(int32_t writtenSamples, int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { int32_t mChannels = 2; + // Read Chart ID from input 3 + mChartId = static_cast(inputs.at(3).Get()); + outputs.at(0).MinimizeBuffer(numCacheSamples * mChannels); float* out = &outputs.at(0).Get(numCacheSamples * mChannels); @@ -111,13 +114,63 @@ namespace l::nodegraph { auto buf = out + writtenSamples * mChannels; int32_t j = 0; for (j = 0; j < numSamples; j++) { - auto unixtimef = *input[0]; - auto unixtime = l::math::algorithm::convert(unixtimef); - if (unixtimef == 0.0f || mLatestUnixtime >= unixtime) { - mLatestUnixtime = unixtime; - break; + for (int32_t i = 0; i < mChannels; i++) { + *buf++ = *input[i]++; } - mLatestUnixtime = unixtime; + } + for (; j < numSamples; j++) { + for (int32_t i = 0; i < mChannels; i++) { + *buf++ = 0.0f; + } + } + } + + /*********************************************************************/ + void GraphUIChartLine2::ProcessWriteCached(int32_t writtenSamples, int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + int32_t mChannels = 3; + + // Read Chart ID from input 4 + mChartId = static_cast(inputs.at(4).Get()); + + outputs.at(0).MinimizeBuffer(numCacheSamples * mChannels); + float* out = &outputs.at(0).Get(numCacheSamples * mChannels); + + float* input[3]; + for (int32_t j = 0; j < mChannels; j++) { + input[j] = &inputs.at(j).Get(numSamples); + } + auto buf = out + writtenSamples * mChannels; + int32_t j = 0; + for (j = 0; j < numSamples; j++) { + for (int32_t i = 0; i < mChannels; i++) { + *buf++ = *input[i]++; + } + } + for (; j < numSamples; j++) { + for (int32_t i = 0; i < mChannels; i++) { + *buf++ = 0.0f; + } + } + } + + + /*********************************************************************/ + void GraphUIChartLine3::ProcessWriteCached(int32_t writtenSamples, int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { + int32_t mChannels = 4; + + // Read Chart ID from input 5 + mChartId = static_cast(inputs.at(5).Get()); + + outputs.at(0).MinimizeBuffer(numCacheSamples * mChannels); + float* out = &outputs.at(0).Get(numCacheSamples * mChannels); + + float* input[4]; + for (int32_t j = 0; j < mChannels; j++) { + input[j] = &inputs.at(j).Get(numSamples); + } + auto buf = out + writtenSamples * mChannels; + int32_t j = 0; + for (j = 0; j < numSamples; j++) { for (int32_t i = 0; i < mChannels; i++) { *buf++ = *input[i]++; } @@ -133,6 +186,9 @@ namespace l::nodegraph { void GraphUICandleSticks::ProcessWriteCached(int32_t writtenSamples, int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector& outputs) { const int32_t stride = 6; + // Read Chart ID from input 7 + mChartId = static_cast(inputs.at(7).Get()); + if (writtenSamples == 0) { outputs.at(0).MinimizeBuffer(numCacheSamples * stride); mLatestUnixtime = 0; @@ -165,4 +221,85 @@ namespace l::nodegraph { } } + /*********************************************************************/ + void GraphUIChartMarkers::Process(int32_t numSamples, int32_t numCacheSamples, std::vector& inputs, std::vector&) { + auto timeInput = &inputs.at(0).Get(numSamples); + auto openInput = &inputs.at(1).Get(numSamples); + auto closeInput = &inputs.at(2).Get(numSamples); + auto entry1Input = &inputs.at(3).Get(numSamples); + auto entry2Input = &inputs.at(4).Get(numSamples); + auto slip = inputs.at(5).Get(); + //auto mainSize = inputs.at(10).Get(); + auto entry3Input = &inputs.at(11).Get(numSamples); + + auto entry1Active = inputs.at(3).HasInputNode(); + auto entry2Active = inputs.at(4).HasInputNode(); + auto entry3Active = inputs.at(11).HasInputNode(); + auto entryShared3 = ((entry1Active && entry2Active) || (entry1Active && entry3Active) || (entry2Active && entry3Active)) ? 0.5f : 1.0f; + auto entryShare = (entry1Active && entry2Active && entry3Active) ? 0.3333f : entryShared3; + + if (mReadSamples == 0) { + mMarkers.clear(); + mEntry1.Reset(entry1Active ? entryShare : 0.0f); + mEntry2.Reset(entry2Active ? entryShare : 0.0f); + mEntry3.Reset(entry3Active ? entryShare : 0.0f); + } + + for (int32_t i = 0; i < numSamples; i++) { + auto time = *timeInput++; + auto unixtime = l::math::algorithm::convert(time); + auto open = *openInput++; + auto close = *closeInput++; + auto entry1 = *entry1Input++; + auto entry2 = *entry2Input++; + auto entry3 = *entry3Input++; + + if (unixtime == 0) { + continue; + } + + auto estimatedPrice = (open + close) * 0.5f; + + mEntry1.Update(entry1, estimatedPrice, unixtime); + mEntry2.Update(entry2, estimatedPrice, unixtime); + mEntry3.Update(entry3, estimatedPrice, unixtime); + + if (mEntry1.TradeEntered(unixtime)) { + auto s = std::make_tuple(unixtime, estimatedPrice, 1.0f, mTotalProfit); + mMarkers.push_back(std::move(s)); + } + if (mEntry2.TradeEntered(unixtime)) { + auto s = std::make_tuple(unixtime, estimatedPrice, 1.0f, mTotalProfit); + mMarkers.push_back(std::move(s)); + } + if (mEntry3.TradeEntered(unixtime)) { + auto s = std::make_tuple(unixtime, estimatedPrice, 1.0f, mTotalProfit); + mMarkers.push_back(std::move(s)); + } + if (mEntry1.TradeExited(unixtime)) { + mTotalProfit *= mEntry1.GetProfit(slip); + auto s = std::make_tuple(unixtime, estimatedPrice, -1.0f, mTotalProfit); + mMarkers.push_back(std::move(s)); + mEntry1.Reset(entry1Active ? entryShare : 0.0f); + } + if (mEntry2.TradeExited(unixtime)) { + mTotalProfit *= mEntry2.GetProfit(slip); + auto s = std::make_tuple(unixtime, estimatedPrice, -1.0f, mTotalProfit); + mMarkers.push_back(std::move(s)); + mEntry2.Reset(entry2Active ? entryShare : 0.0f); + } + if (mEntry3.TradeExited(unixtime)) { + mTotalProfit *= mEntry3.GetProfit(slip); + auto s = std::make_tuple(unixtime, estimatedPrice, -1.0f, mTotalProfit); + mMarkers.push_back(std::move(s)); + mEntry3.Reset(entry3Active ? entryShare : 0.0f); + } + } + + mReadSamples += numSamples; + if (mReadSamples >= numCacheSamples) { + mReadSamples = 0; + mTotalProfit = 1.0f; + } + } } diff --git a/packages/nodegraph/tests/common/NodeGraphBatchingTest.cpp b/packages/nodegraph/tests/common/NodeGraphBatchingTest.cpp index 42d71097..8e071cd9 100644 --- a/packages/nodegraph/tests/common/NodeGraphBatchingTest.cpp +++ b/packages/nodegraph/tests/common/NodeGraphBatchingTest.cpp @@ -24,13 +24,13 @@ TEST(NodeGraphBatching, Simple) { for (int32_t i = 0; i < 4; i++) { - LOG(LogInfo) << "batch " << i; + LLOG(LogInfo) << "batch " << i; group.ProcessSubGraph(8); // 8 samples per batch auto output = &group.GetOutput(0, 8); for (int32_t j = 0; j < 8; j++) { - LOG(LogInfo) << "sample " << (j+(i*8)) << ": " << *output++; + LLOG(LogInfo) << "sample " << (j+(i*8)) << ": " << *output++; } } diff --git a/packages/nodegraph/tests/common/NodeGraphDataTest.cpp b/packages/nodegraph/tests/common/NodeGraphDataTest.cpp index 6d572923..1199a031 100644 --- a/packages/nodegraph/tests/common/NodeGraphDataTest.cpp +++ b/packages/nodegraph/tests/common/NodeGraphDataTest.cpp @@ -34,7 +34,7 @@ class TestOp : public NodeGraphOp2 { *out0++ = value0; *out1++ = value1; - LOG(LogInfo) << "Node input(" << i << "): " << value0 << ", " << value1; + LLOG(LogInfo) << "Node input(" << i << "): " << value0 << ", " << value1; } } protected: @@ -57,7 +57,7 @@ class Copy : public NodeGraphOp { for (int i = 0; i < numSamples; i++) { auto in1 = *in++; *out++ = in1; - LOG(LogInfo) << "Node input(" << i << "): " << in1; + LLOG(LogInfo) << "Node input(" << i << "): " << in1; } } protected: @@ -114,7 +114,7 @@ class TestOp2 : public NodeGraphOp2 { *out0++ = value0; *out1++ = value1; - LOG(LogInfo) << "Node input(" << i << "): " << value0 << ", " << value1; + LLOG(LogInfo) << "Node input(" << i << "): " << value0 << ", " << value1; } } protected: @@ -165,7 +165,7 @@ TEST(NodeGraphData, CandleStickData) { auto out = &node1.GetOutput(0, 5); for (int i = 0; i < 5; i++) { - LOG(LogInfo) << "node1(0):" << *out++; + LLOG(LogInfo) << "node1(0):" << *out++; } } diff --git a/packages/nodegraph/tests/common/NodeGraphOpTest.cpp b/packages/nodegraph/tests/common/NodeGraphOpTest.cpp new file mode 100644 index 00000000..bbd6ce16 --- /dev/null +++ b/packages/nodegraph/tests/common/NodeGraphOpTest.cpp @@ -0,0 +1,77 @@ +#include "testing/Test.h" +#include "logging/Log.h" + +#include "nodegraph/core/NodeGraphData.h" +#include "nodegraph/core/NodeGraphInput.h" +#include "nodegraph/core/NodeGraphOutput.h" +#include "nodegraph/core/NodeGraphBase.h" +#include +#include + +using namespace l; +using namespace l::nodegraph; + + +float MAFilter(float width, float weightAccent, float balance, float gamma) { + int32_t widthInt = 1 + static_cast(width); + int32_t bufferSize = widthInt; + float widthFrac = width - l::math::floor(width); + + float state = 100.0f; + float inputWeight = 1.0f; + + float outVal = 0.0; + float balanceFactor = 1.0f - balance; + float balanceDelta = balance / static_cast(widthInt - 1); + float balanceDivisorSum = 0.0f; + float weight = l::math::pow(inputWeight, weightAccent); + + { // remove a part of the first sample of the sum as it is not part of the moving average + balanceFactor -= balanceDelta * widthFrac; + + auto fac = weight * balanceFactor; + auto sign = l::math::functions::sign(fac); + fac = l::math::pow(fac * sign, gamma); + fac *= sign; + outVal = fac * state * widthFrac; + balanceDivisorSum = fac * widthFrac; + + balanceFactor += balanceDelta * widthFrac; + } + for (int32_t j = 0; j < bufferSize; j++) { + float weight = l::math::pow(inputWeight, weightAccent); + auto fac = weight * balanceFactor; + auto sign = l::math::functions::sign(fac); + fac = l::math::pow(fac * sign, gamma); + fac *= sign; + outVal += fac * state; + balanceDivisorSum += fac; + balanceFactor += balanceDelta; + } + + {// Check that balanceFactor summed to 1 (but first subtract the last unused addition) + balanceFactor -= balanceDelta; + TEST_FUZZY(balanceFactor, 1.0f, 0.001f, ""); + } + + auto signal = outVal / balanceDivisorSum; + + return signal; +} + +TEST(NodeGraphOp, MAFilterSumming) { + + for (int32_t i = 0; i < 3; i++) { + MAFilter(4.3f + i * 1.3f, 1.0f, 1.5f, 1.0f); + } + for (int32_t i = 0; i < 3; i++) { + auto v0 = MAFilter(4.3f + i * 1.3f, 1.0f, 1.5f, 1.0f); + auto v1 = MAFilter(4.3f + i * 1.3f, 1.0f, 1.5f, 1.000001f); + TEST_FUZZY(v0, v1, 0.0001f, ""); + } + for (int32_t i = 0; i < 3; i++) { + MAFilter(4.3f + i * 1.3f, 1.4f, 1.5f, 3.0f); + } + + return 0; +} diff --git a/packages/nodegraph/tests/common/NodeGraphSchemaTest.cpp b/packages/nodegraph/tests/common/NodeGraphSchemaTest.cpp index ce86c37b..683c89eb 100644 --- a/packages/nodegraph/tests/common/NodeGraphSchemaTest.cpp +++ b/packages/nodegraph/tests/common/NodeGraphSchemaTest.cpp @@ -81,7 +81,7 @@ TEST(NodeGraph, NumericIntegral) { for (int i = 0; i < 30; i++) { input = sinf(2.0f * i * oneRev); nodeIntegral.ProcessSubGraph(1, 30); - //LOG(LogInfo) << nodeIntegral.Get(0); + //LLOG(LogInfo) << nodeIntegral.Get(0); } TEST_FUZZY(nodeIntegral.GetOutput(0), 0.00323272f, 0.0001f, ""); @@ -104,7 +104,7 @@ TEST(NodeGraph, FilterLowpass) { for (int i = 0; i < 30; i++) { input = sinf(2.0f * i * oneRev); nodeLowpass.ProcessSubGraph(1, 30); - //LOG(LogInfo) << nodeLowpass.GetOutput(0); + //LLOG(LogInfo) << nodeLowpass.GetOutput(0); } TEST_FUZZY(nodeLowpass.GetOutput(0), -0.287209, 0.0001f, ""); diff --git a/packages/nodegraph/tests/common/NodeGraphSerializationTest.cpp b/packages/nodegraph/tests/common/NodeGraphSerializationTest.cpp index 4159318a..bdb65575 100644 --- a/packages/nodegraph/tests/common/NodeGraphSerializationTest.cpp +++ b/packages/nodegraph/tests/common/NodeGraphSerializationTest.cpp @@ -32,8 +32,8 @@ TEST(NodeGraph, SerializationBasic) { builder.End(); auto d = builder.GetStr(); - LOG(LogInfo) << "Archive:"; - LOG(LogInfo) << "\n" << d; + LLOG(LogInfo) << "Archive:"; + LLOG(LogInfo) << "\n" << d; l::serialization::JsonParser<100> parser; auto [result, error] = parser.LoadJson(d.c_str(), d.size()); diff --git a/packages/physics/include/physics/Integration.h b/packages/physics/include/physics/Integration.h index 0e8c1638..72a94042 100644 --- a/packages/physics/include/physics/Integration.h +++ b/packages/physics/include/physics/Integration.h @@ -139,11 +139,11 @@ namespace physics { lambda *= 1.0 / forceConstant; V forceLimit = static_cast(1.0 / epsilon2); if (lambda > forceLimit) { - LOG(LogInfo) << "Force limit exceeded: " << lambda; + LLOG(LogInfo) << "Force limit exceeded: " << lambda; //lambda = forceLimit; } if (lambda < -forceLimit) { - LOG(LogInfo) << "Force limit exceeded: " << lambda; + LLOG(LogInfo) << "Force limit exceeded: " << lambda; //lambda = -forceLimit; } @@ -594,7 +594,7 @@ namespace physics { V dP0Sqr = dP0.sqr(); V dP1Sqr = dP1.sqr(); if (abs(dP0Sqr - dP1Sqr) > 0.00001) { - LOG(LogError) << "Something went wrong"; + LLOG(LogError) << "Something went wrong"; } } } @@ -629,7 +629,7 @@ namespace physics { V dP0Sqr = dP0.sqr(); V dP1Sqr = dP1.sqr(); if (abs(dP0Sqr - dP1Sqr) > 0.01) { - LOG(LogError) << "Something went wrong"; + LLOG(LogError) << "Something went wrong"; } } } diff --git a/packages/physics/tests/common/BoxSweepTest.cpp b/packages/physics/tests/common/BoxSweepTest.cpp index 6b5ceb0a..c2268e66 100644 --- a/packages/physics/tests/common/BoxSweepTest.cpp +++ b/packages/physics/tests/common/BoxSweepTest.cpp @@ -39,7 +39,7 @@ TEST(BoxSweep, Init) { sweeper.update(); sweeper.overlapAction([](uint32_t id0, uint32_t id1) { - LOG(LogInfo) << "id0:" << id0 << ", id1:" << id1; + LLOG(LogInfo) << "id0:" << id0 << ", id1:" << id1; }); return 0; } diff --git a/packages/physics/tests/common/GridMapTest.cpp b/packages/physics/tests/common/GridMapTest.cpp index 72effd6d..b73407e3 100644 --- a/packages/physics/tests/common/GridMapTest.cpp +++ b/packages/physics/tests/common/GridMapTest.cpp @@ -145,7 +145,7 @@ TEST(GridMap, StressTest) { count++; }); - LOG(LogInfo) << "Found " << count << " pairs"; + LLOG(LogInfo) << "Found " << count << " pairs"; TEST_TRUE(count > 500 && count < 1100, ""); return 0; diff --git a/packages/physics/tests/common/VectorTest.cpp b/packages/physics/tests/common/VectorTest.cpp index 4585938d..b128f282 100644 --- a/packages/physics/tests/common/VectorTest.cpp +++ b/packages/physics/tests/common/VectorTest.cpp @@ -10,7 +10,7 @@ using namespace l; TEST(Vector, Data2) { { l::vec::Data2 x(5.3, 4.4); - LOG(LogInfo) << x.to_string(); + LLOG(LogInfo) << x.to_string(); TEST_FUZZY(x.floor(1).x1, 5.0, EPSILON, "floor failed"); TEST_FUZZY(x.floor(1).x2, 4.0, EPSILON, "floor failed"); @@ -29,7 +29,7 @@ TEST(Vector, Data2) { } { l::vec::Data2 x(-5.3, -4.4); - LOG(LogInfo) << x.to_string(); + LLOG(LogInfo) << x.to_string(); TEST_FUZZY(x.floor(1).x1, -6.0, 0.001, "floor failed"); TEST_FUZZY(x.floor(1).x2, -5.0, 0.001, "floor failed"); @@ -48,7 +48,7 @@ TEST(Vector, Data2) { TEST(Vector, Data4) { { l::vec::Data4 x(5.3, 4.4, 5.3, 4.4); - LOG(LogInfo) << x.to_string(); + LLOG(LogInfo) << x.to_string(); TEST_FUZZY(x.floor(1).x1, 5.0, EPSILON, "floor failed"); TEST_FUZZY(x.floor(1).x2, 4.0, EPSILON, "floor failed"); @@ -67,7 +67,7 @@ TEST(Vector, Data4) { } { l::vec::Data4 x(-5.3, -4.4, -5.3, -4.4); - LOG(LogInfo) << x.to_string(); + LLOG(LogInfo) << x.to_string(); TEST_FUZZY(x.floor(1).x1, -6.0, 0.001, "floor failed"); TEST_FUZZY(x.floor(1).x2, -5.0, 0.001, "floor failed"); diff --git a/packages/rendering/include/rendering/DataConversion.h b/packages/rendering/include/rendering/DataConversion.h index 6da674f1..c961e810 100644 --- a/packages/rendering/include/rendering/DataConversion.h +++ b/packages/rendering/include/rendering/DataConversion.h @@ -106,7 +106,7 @@ namespace rendering { dstSize = index1 > dstSize ? index1 + 1 : dstSize; SourceInnerType* innerTypePtr = reinterpret_cast(src2 + index0); if (index1 * innerCount >= result.size()) { - LOG(LogError) << "Index too large?"; + LLOG(LogError) << "Index too large?"; result.resize((index1 + index1 / 4) * innerCount); } for (size_t j = 0; j < innerCount; j++) { diff --git a/packages/rendering/include/rendering/GLFWShaders.h b/packages/rendering/include/rendering/GLFWShaders.h index 825c4915..182010a0 100644 --- a/packages/rendering/include/rendering/GLFWShaders.h +++ b/packages/rendering/include/rendering/GLFWShaders.h @@ -65,7 +65,7 @@ namespace rendering { GLsizei logLength = 0; GLchar message[1024]; glGetShaderInfoLog(mVertexShaderId, 1024, &logLength, message); - LOG(LogError) << "Failed to compile vertex shader. Error: " << message; + LLOG(LogError) << "Failed to compile vertex shader. Error: " << message; } GL_ASSERT(); } @@ -79,7 +79,7 @@ namespace rendering { GLsizei logLength = 0; GLchar message[1024]; glGetShaderInfoLog(mFragmentShaderId, 1024, &logLength, message); - LOG(LogError) << "Failed to compile fragment shader. Error: " << message; + LLOG(LogError) << "Failed to compile fragment shader. Error: " << message; } GL_ASSERT(); } @@ -97,7 +97,7 @@ namespace rendering { GLchar message[1024]; glGetProgramInfoLog(mId, 1024, &logLength, message); - LOG(LogError) << "Failed to link. Error: " << message; + LLOG(LogError) << "Failed to link. Error: " << message; } GL_ASSERT(); } diff --git a/packages/rendering/include/rendering/GLFWWindow.h b/packages/rendering/include/rendering/GLFWWindow.h index cb055264..f73b036d 100644 --- a/packages/rendering/include/rendering/GLFWWindow.h +++ b/packages/rendering/include/rendering/GLFWWindow.h @@ -19,6 +19,7 @@ namespace rendering { using KeyCB = void(GLFWwindow* window, int key, int scancode, int action, int mods); using MouseCB = void(GLFWwindow* window, int button, int action, int mods); using ScrollCB = void(GLFWwindow* window, float xoffset, float yoffset); + using CharsCB = void(GLFWwindow* window, unsigned int codepoint); class GLFWWindowHandle { public: @@ -36,9 +37,12 @@ namespace rendering { GLFWwindow* get(); // setup - void SetInput(std::function keyCallback = nullptr, + void SetInput( + std::function keyCallback = nullptr, std::function mouseCallback = nullptr, - std::function scrollCallback = nullptr); + std::function scrollCallback = nullptr, + std::function charsCallback = nullptr + ); void SetOpacity(float opacity); // control diff --git a/packages/rendering/include/rendering/GeometryManip.h b/packages/rendering/include/rendering/GeometryManip.h index de321c75..8287ef9c 100644 --- a/packages/rendering/include/rendering/GeometryManip.h +++ b/packages/rendering/include/rendering/GeometryManip.h @@ -197,7 +197,7 @@ namespace rendering { vec3_mul_cross(tmp, &normals[i], &tangent[i]); float dot2 = vec3_mul_inner(tmp, &tan2[i]); if (dot2 == 0) { - LOG(LogWarning) << "Vertex index " << i << " has an undefined bitangent handedness because:"; + LLOG(LogWarning) << "Vertex index " << i << " has an undefined bitangent handedness because:"; dot2 = 1.0; } if (dot2 < 0) { diff --git a/packages/rendering/include/rendering/ui/UIBase.h b/packages/rendering/include/rendering/ui/UIBase.h index 0940d285..bf44821f 100644 --- a/packages/rendering/include/rendering/ui/UIBase.h +++ b/packages/rendering/include/rendering/ui/UIBase.h @@ -22,6 +22,7 @@ namespace l::ui { void UIAdopt(std::unique_ptr&& ui); void UIErase(l::ui::UIBase* ui); void UIErase(std::string_view name); + void UIDestroy(); void UIHouseKeeping(); UIBase* UIGet(std::string_view name); bool UIHas(std::string_view name); diff --git a/packages/rendering/include/rendering/ui/UIContainer.h b/packages/rendering/include/rendering/ui/UIContainer.h index d817cc59..80f65980 100644 --- a/packages/rendering/include/rendering/ui/UIContainer.h +++ b/packages/rendering/include/rendering/ui/UIContainer.h @@ -247,8 +247,9 @@ namespace l::ui { const uint32_t UIContainer_OutputFlag = 0x00000400; const uint32_t UIContainer_LinkFlag = 0x00000800; const uint32_t UIContainer_SelectFlag = 0x00001000; - const uint32_t UIContainer_EditFlag = 0x00002000; + const uint32_t UIContainer_TouchEditFlag = 0x00002000; const uint32_t UIContainer_ConstantsKeyboardFlag = 0x00004000; + const uint32_t UIContainer_TextEditFlag = 0x00008000; class UIDraw; @@ -373,7 +374,7 @@ namespace l::ui { void SetParent(UIContainer* parent) {mParent = parent;} void SetCoParent(UIContainer* coParent) {mCoParent = coParent;} - void DebugLog() { LOG(LogDebug) << "UIContainer: " << mDisplayName << ", [" << mDisplayArea.mScale << "][" << mDisplayArea.mPosition.x << ", " << mDisplayArea.mPosition.y << "][" << mDisplayArea.mSize.x << ", " << mDisplayArea.mSize.y << "]"; } + void DebugLog() { LLOG(LogDebug) << "UIContainer: " << mDisplayName << ", [" << mDisplayArea.mScale << "][" << mDisplayArea.mPosition.x << ", " << mDisplayArea.mPosition.y << "][" << mDisplayArea.mSize.x << ", " << mDisplayArea.mSize.y << "]"; } protected: int32_t mId = 0; int32_t mNodeId = -1; diff --git a/packages/rendering/include/rendering/ui/UICreator.h b/packages/rendering/include/rendering/ui/UICreator.h index fa92f611..c5a83bcd 100644 --- a/packages/rendering/include/rendering/ui/UICreator.h +++ b/packages/rendering/include/rendering/ui/UICreator.h @@ -9,5 +9,5 @@ namespace l::ui { - UIHandle CreateUINode(UIManager& uiManager, l::nodegraph::NodeGraphBase& node, ImVec2 p); + UIHandle CreateUINode(UIManager& uiManager, l::nodegraph::NodeGraphBase& node, ImVec2 p, ImVec2 s = ImVec2(0.0f, 0.0f)); } diff --git a/packages/rendering/include/rendering/ui/UIModule.h b/packages/rendering/include/rendering/ui/UIModule.h deleted file mode 100644 index 9dbd8e13..00000000 --- a/packages/rendering/include/rendering/ui/UIModule.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -namespace l::ui { - - class UIModule { - public: - UIModule() = default; - virtual ~UIModule() = default; - - virtual void UIModuleRenderControls(bool widget) = 0; - virtual void UIModuleRenderChart(std::string_view plotKey, float candleWidth) = 0; - }; -} diff --git a/packages/rendering/include/rendering/ui/UINodeEditor.h b/packages/rendering/include/rendering/ui/UINodeEditor.h index 209977ba..031163e4 100644 --- a/packages/rendering/include/rendering/ui/UINodeEditor.h +++ b/packages/rendering/include/rendering/ui/UINodeEditor.h @@ -17,7 +17,7 @@ namespace l::ui { - void depthFirstTraversal(const nodegraph::TreeMenuNode& node, std::vector& path, std::function cbMenuItem); + void depthFirstTraversal(const nodegraph::TreeMenuNode& node, std::vector& path, std::function cbMenuItem); struct NodeEvent { l::nodegraph::NodeGraphSchema* mNodeSchema = nullptr; @@ -54,11 +54,14 @@ namespace l::ui { UIDrag mDragVisitor; UIMove mMoveVisitor; UIResize mResizeVisitor; - UIEdit mEditVisitor; + UITouchEdit mTouchEditVisitor; + UITextEdit mTextEditVisitor; l::nodegraph::NodeGraphSchema* mNGSchema = nullptr; std::vector> mEventListeners; std::function mOverlayContentWindow = nullptr; + + //l::string::string_buffer<20> mPickerSearch; }; } diff --git a/packages/rendering/include/rendering/ui/UIVisitors.h b/packages/rendering/include/rendering/ui/UIVisitors.h index 29028b29..70e7cc96 100644 --- a/packages/rendering/include/rendering/ui/UIVisitors.h +++ b/packages/rendering/include/rendering/ui/UIVisitors.h @@ -86,7 +86,7 @@ namespace l::ui { std::function mRemoveHandler = nullptr; }; - class UIEdit : public UIVisitor { + class UITouchEdit : public UIVisitor { public: virtual bool Visit(UIContainer& container, const InputState& input); virtual void Reset(); @@ -101,6 +101,28 @@ namespace l::ui { std::function mEditHandler = nullptr; }; + class UITextEdit : public UIVisitor { + public: + virtual bool Visit(UIContainer& container, const InputState& input); + virtual void Reset(); + + void SetEditHandler(std::function handler) { + mEditHandler = handler; + } + + protected: + std::string mEditedText; + bool mEditing = false; + UIContainer* mSourceContainer = nullptr; + std::function mEditHandler = nullptr; + }; + + enum class UIDrawMode { + All, // Draw everything + LinksOnly, // Only draw link containers (for rendering behind nodes) + NoLinks // Draw everything except links (for rendering in front) + }; + class UIDraw : public UIVisitor { public: UIDraw(ImDrawList* drawList = nullptr) : mDrawList(drawList) {} @@ -112,6 +134,10 @@ namespace l::ui { mDrawList = drawList; } + void SetDrawMode(UIDrawMode mode) { + mDrawMode = mode; + } + void SetDrawChannelTextHandler(std::function handler) { mDrawChannelTextHandler = handler; } @@ -124,9 +150,10 @@ namespace l::ui { } protected: ImDrawList* mDrawList; + UIDrawMode mDrawMode = UIDrawMode::All; std::function mDrawChannelTextHandler = nullptr; std::function mDrawLineHandler = nullptr; - ImColor mSelectColor = ImColor(darkGrey); + ImColor mSelectColor = ImColor(pastellYellow); }; class UILinkIO : public UIVisitor { diff --git a/packages/rendering/source/common/FPSInterface.cpp b/packages/rendering/source/common/FPSInterface.cpp index 7969a0be..00e82e6a 100644 --- a/packages/rendering/source/common/FPSInterface.cpp +++ b/packages/rendering/source/common/FPSInterface.cpp @@ -69,11 +69,11 @@ namespace l { } void FPSInterface::HandleTouch(int button, int action, int mods) { - LOG(LogInfo) << "button:" << button << ", action:" << action << ", mods:" << mods; + LLOG(LogInfo) << "button:" << button << ", action:" << action << ", mods:" << mods; } void FPSInterface::HandleScroll(float x, float y) { - LOG(LogInfo) << "xoffset:" << x << ", yoffset:" << y; + LLOG(LogInfo) << "xoffset:" << x << ", yoffset:" << y; } void FPSInterface::HandleKeyPress(int key, int, int action, int) { @@ -174,7 +174,7 @@ namespace l { mTurnAngle += x * f; mTiltAngle += y * f; if (fabs(x) > 0.0001f && fabs(y) > 0.0001f) { - //LOG(LogInfo) << "Camera direction: [" << mFront[0] << "," << mFront[1] << "," << mFront[2] << "]" << "[" << mRight[0] << "," << mRight[1] << "," << mRight[2] << "]"; + //LLOG(LogInfo) << "Camera direction: [" << mFront[0] << "," << mFront[1] << "," << mFront[2] << "]" << "[" << mRight[0] << "," << mRight[1] << "," << mRight[2] << "]"; } } @@ -252,7 +252,7 @@ namespace l { mVelocity[1] += velocityDirection[1] * acceleration; mVelocity[2] += velocityDirection[2] * acceleration; - //LOG(LogInfo) << "Camera velocity: [" << mPosition[0] << "," << mPosition[1] << "," << mPosition[2] << "]"; + //LLOG(LogInfo) << "Camera velocity: [" << mPosition[0] << "," << mPosition[1] << "," << mPosition[2] << "]"; } } } diff --git a/packages/rendering/source/common/GLFWRenderVAO.cpp b/packages/rendering/source/common/GLFWRenderVAO.cpp index b844dca9..aa9bf28b 100644 --- a/packages/rendering/source/common/GLFWRenderVAO.cpp +++ b/packages/rendering/source/common/GLFWRenderVAO.cpp @@ -132,10 +132,10 @@ namespace l { uint32_t index = mesh.vertex_indices.data[i]; const ufbx_vec2& value = mesh.vertex_uv[i]; if (uvs[index*2] != static_cast(value.x)) { - LOG(LogError) << ""; + LLOG(LogError) << ""; } if (uvs[index * 2+1] < static_cast(value.y)) { - LOG(LogError) << ""; + LLOG(LogError) << ""; } uvs[index * 2 + 0] = static_cast(value.x); uvs[index * 2 + 1] = static_cast(value.y); diff --git a/packages/rendering/source/common/GLFWWindow.cpp b/packages/rendering/source/common/GLFWWindow.cpp index 35f1961b..dfdd8de3 100644 --- a/packages/rendering/source/common/GLFWWindow.cpp +++ b/packages/rendering/source/common/GLFWWindow.cpp @@ -22,26 +22,31 @@ namespace { void glfw_error_callback(int error, const char* description) { - LOG(LogError) << "GLFW Error " << error << ":" << description << "\n"; + LLOG(LogError) << "GLFW Error " << error << ":" << description << "\n"; } struct Callbacks { std::function key; std::function mouse; std::function scroll; + std::function chars; }; std::unordered_map sCallbacks; std::atomic_int sGLFWWindowCount = 0; - void set_callbacks(GLFWwindow* window, std::function key = nullptr, - std::function mouse = nullptr, - std::function scroll = nullptr) { - if (key || mouse || scroll) { + void set_callbacks(GLFWwindow* window, + std::function key = nullptr, + std::function mouse = nullptr, + std::function scroll = nullptr, + std::function chars = nullptr + ) { + if (chars || key || mouse || scroll) { Callbacks cb; cb.key = std::move(key); cb.mouse = std::move(mouse); cb.scroll = std::move(scroll); + cb.chars = std::move(chars); sCallbacks.emplace(window, std::move(cb)); } else { @@ -74,6 +79,14 @@ namespace { it->second.scroll(window, static_cast(xoffset), static_cast(yoffset)); } } + + static void invoke_chars(GLFWwindow* window, unsigned int codepoint) { + auto it = sCallbacks.find(window); + if (it != sCallbacks.end() && it->second.chars) { + it->second.chars(window, codepoint); + } + } + } namespace l { @@ -107,7 +120,7 @@ namespace l { // Safe to call even if glfw is already initialized glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) { - LOG(LogError) << "Failed to initialize GLFW"; + LLOG(LogError) << "Failed to initialize GLFW"; return nullptr; } @@ -147,24 +160,25 @@ namespace l { } if (window == nullptr) { - LOG(LogError) << "Failed to create GLFW window"; + LLOG(LogError) << "Failed to create GLFW window"; return nullptr; } glfwMakeContextCurrent(window); - LOG(LogInfo) << "GLFW version:" << glfwGetVersionString(); + LLOG(LogInfo) << "GLFW version:" << glfwGetVersionString(); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - LOG(LogInfo) << "Failed to load OpenGL procedures"; + LLOG(LogInfo) << "Failed to load OpenGL procedures"; return nullptr; } - LOG(LogInfo) << "OpenGL version:" << glGetString(GL_VERSION); + LLOG(LogInfo) << "OpenGL version:" << glGetString(GL_VERSION); glfwSetKeyCallback(window, invoke_key); glfwSetMouseButtonCallback(window, invoke_mouse); glfwSetScrollCallback(window, invoke_scroll); + glfwSetCharCallback(window, invoke_chars); //l::rendering::read("images/favicon.ico") //GLFWimage icon; @@ -202,11 +216,13 @@ namespace l { void GLFWWindowHandle::SetInput( std::function keyCallback, std::function mouseCallback, - std::function scrollCallback) { + std::function scrollCallback, + std::function charsCallback + ) { if (!IsValid()) { return; } - set_callbacks(mHandle.get(), std::move(keyCallback), std::move(mouseCallback), std::move(scrollCallback)); + set_callbacks(mHandle.get(), std::move(keyCallback), std::move(mouseCallback), std::move(scrollCallback), std::move(charsCallback)); SetMouseMode(); diff --git a/packages/rendering/source/common/Geometry.cpp b/packages/rendering/source/common/Geometry.cpp index 83670dce..a3773453 100644 --- a/packages/rendering/source/common/Geometry.cpp +++ b/packages/rendering/source/common/Geometry.cpp @@ -89,7 +89,7 @@ namespace l { // If the import failed, report it if (nullptr == scene) { - LOG(LogError) << importer.GetErrorString(); + LLOG(LogError) << importer.GetErrorString(); return false; } @@ -107,7 +107,7 @@ namespace l { LoadColladaAsset(file, [](const ColladaData& colladaData) { for (auto& geometry : colladaData.mGeometryNodes) { - LOG(LogInfo) << "Model '" << geometry.mName << "' was successfully loaded."; + LLOG(LogInfo) << "Model '" << geometry.mName << "' was successfully loaded."; } }); @@ -316,15 +316,15 @@ namespace l { // Calculate handedness if (dot2 == 0) { - LOG(LogWarning) << "Vertex index " << i << " has an undefined bitangent handedness because:"; + LLOG(LogWarning) << "Vertex index " << i << " has an undefined bitangent handedness because:"; if (l::vec::IsZeroVector(vertices.subspan(index0, 3))) { - LOG(LogError) << " Normal is zero"; + LLOG(LogError) << " Normal is zero"; } if (l::vec::IsZeroVector(vertices.subspan(index1, 3))) { - LOG(LogWarning) << " Tangent is zero"; + LLOG(LogWarning) << " Tangent is zero"; } if (l::vec::IsZeroVector(std::span(tan2.begin() + 3 * i, 3))) { - LOG(LogWarning) << " Bitangent is zero"; + LLOG(LogWarning) << " Bitangent is zero"; } } if (dot2 < 0) { diff --git a/packages/rendering/source/common/ImageSupport.cpp b/packages/rendering/source/common/ImageSupport.cpp index 704f8b60..8c9118a1 100644 --- a/packages/rendering/source/common/ImageSupport.cpp +++ b/packages/rendering/source/common/ImageSupport.cpp @@ -81,14 +81,14 @@ namespace l::rendering { lodepng::State state; auto result = lodepng_inspect(&w, &h, &state, data.data(), data.size()); if (result != 0) { - LOG(LogError) << "Failed to load image header"; + LLOG(LogError) << "Failed to load image header"; return false; } unsigned int e = lodepng::decode(pixels, w, h, data); if (e != 0) { - LOG(LogError) << "Failed loading PNG file %s"; + LLOG(LogError) << "Failed loading PNG file %s"; return false; } diff --git a/packages/rendering/source/common/ui/UIBase.cpp b/packages/rendering/source/common/ui/UIBase.cpp index f51680b7..855ba99b 100644 --- a/packages/rendering/source/common/ui/UIBase.cpp +++ b/packages/rendering/source/common/ui/UIBase.cpp @@ -39,6 +39,11 @@ namespace l::ui { mUIDeleted.clear(); } + void UIDestroy() { + std::lock_guard lock(mUIsMutex); + mUIs.clear(); + } + UIBase* UIGet(std::string_view name) { std::lock_guard lock(mUIsMutex); for (auto& ui : mUIs) { diff --git a/packages/rendering/source/common/ui/UICreator.cpp b/packages/rendering/source/common/ui/UICreator.cpp index c522b236..b8eb8e45 100644 --- a/packages/rendering/source/common/ui/UICreator.cpp +++ b/packages/rendering/source/common/ui/UICreator.cpp @@ -8,7 +8,7 @@ namespace l::ui { - UIHandle CreateUINode(UIManager& uiManager, l::nodegraph::NodeGraphBase& node, ImVec2 p) { + UIHandle CreateUINode(UIManager& uiManager, l::nodegraph::NodeGraphBase& node, ImVec2 p, ImVec2 s) { auto numInputChannels = node.GetNumInputs(); auto numOutputChannels = node.GetNumOutputs(); @@ -65,13 +65,23 @@ namespace l::ui { } if (node.IsInputDataVisible(i)) { - auto inputDataText = CreateContainer(uiManager, (node.IsInputDataEditable(i) ? l::ui::UIContainer_EditFlag : 0) | l::ui::UIContainer_DrawFlag, l::ui::UIRenderType::NodeOutputValue, l::ui::UIAlignH::Left); + bool isText = node.IsInputDataText(i); + bool isEditable = node.IsInputDataEditable(i); + + auto inputDataText = CreateContainer(uiManager, (isEditable ? (isText ? l::ui::UIContainer_TextEditFlag : l::ui::UIContainer_TouchEditFlag) : 0) | l::ui::UIContainer_DrawFlag, l::ui::UIRenderType::NodeOutputValue, l::ui::UIAlignH::Left); inputDataText->SetColor(mediumWhite); inputDataText->SetPosition(ImVec2(estimatedWidth, 0.0f)); - inputDataText->SetSize(ImVec2(10 * 7, 14.0f)); + if (isText) { + auto textSize = ImGui::CalcTextSize(node.GetInputText(i).data()); + inputDataText->SetSize(ImVec2(textSize.x < 100.0f ? 100.0f : textSize.x, 14.0f)); + estimatedWidth += textSize.x; + } + else { + inputDataText->SetSize(ImVec2(10.0f * 4 + 10.0f, 14.0f)); + estimatedWidth += 10.0f * 4 + 10.0f; + } inputDataText->SetNodeId(node.GetId()); inputDataText->SetChannelId(i); - estimatedWidth += 10 * 7 + 10; row->Add(inputDataText); } @@ -118,6 +128,12 @@ namespace l::ui { } sizeEstimate.x += node4->GetContainerArea().mMargin * 2 + 2.0f; + if (s.x > 0.0f) { + sizeEstimate.x = s.x; + } + if (s.y > sizeEstimate.y) { + sizeEstimate.y = s.y; + } node4->SetSize(sizeEstimate); return node4; diff --git a/packages/rendering/source/common/ui/UINodeEditor.cpp b/packages/rendering/source/common/ui/UINodeEditor.cpp index afa08295..abb90988 100644 --- a/packages/rendering/source/common/ui/UINodeEditor.cpp +++ b/packages/rendering/source/common/ui/UINodeEditor.cpp @@ -1,15 +1,17 @@ #include "rendering/ui/UINodeEditor.h" +#include "rendering/ImguiSpectrum.h" + #include namespace l::ui { - void depthFirstTraversal(const nodegraph::TreeMenuNode& node, std::vector& path, std::function cbMenuItem) { + void depthFirstTraversal(const nodegraph::TreeMenuNode& node, std::vector& path, std::function cbMenuItem) { if (node.GetPathPart().empty()) { for (const auto& child : node.mChildren) { depthFirstTraversal(child, path, cbMenuItem); } - cbMenuItem(node.GetName(), node.GetId()); + cbMenuItem(node.GetName(), node.GetId(), node.GetDescription()); } else { path.emplace_back(node.GetPathPart()); @@ -36,6 +38,10 @@ namespace l::ui { mUIRoot->SetLayoutSize(GetSize()); mUIRoot->SetLayoutPosition(GetPosition()); mUIRoot->Accept(updateVisitor, mUIInput, l::ui::UITraversalMode::BFS); + // Two-pass rendering: draw links first (behind), then nodes (in front) + mDrawVisitor.SetDrawMode(UIDrawMode::LinksOnly); + mUIRoot->Accept(mDrawVisitor, mUIInput, l::ui::UITraversalMode::BFS); + mDrawVisitor.SetDrawMode(UIDrawMode::NoLinks); mUIRoot->Accept(mDrawVisitor, mUIInput, l::ui::UITraversalMode::BFS); ImGui::PopItemWidth(); @@ -48,6 +54,8 @@ namespace l::ui { SetPointerPopup([&]() { ImGui::Text("Node picker"); +// ImGui::SameLine(); +// ImGui::InputText("##pickersearchbar", mPickerSearch.data(), 20); ImGui::Separator(); if (mNGSchema == nullptr) { @@ -55,28 +63,45 @@ namespace l::ui { } std::vector path; - depthFirstTraversal(mNGSchema->GetPickerRoot(), path, [&](std::string_view menuName, int32_t menuId) { - if (!menuName.empty() && ImGui::MenuItem(menuName.data())) { - ImVec2 p = ImVec2(mUIInput.mCurPos.x - GetPosition().x, mUIInput.mCurPos.y - GetPosition().y); - p.x -= mUIRoot->GetPosition().x; - p.y -= mUIRoot->GetPosition().y; - p.x /= mUIRoot->GetScale(); - p.y /= mUIRoot->GetScale(); - p.x -= 3.0f; - p.y -= 3.0f; - auto nodeId = mNGSchema->NewNode(menuId); - auto node = mNGSchema->GetNode(nodeId); - if (node != nullptr) { - auto uiNode = l::ui::CreateUINode(mUIManager, *node, p); - mUIRoot->Add(uiNode); - - auto& uiData = node->GetUIData(); - auto position = uiNode->GetPosition(); - auto size = uiNode->GetSize(); - uiData.x = position.x; - uiData.y = position.y; - uiData.w = size.x; - uiData.h = size.y; + depthFirstTraversal(mNGSchema->GetPickerRoot(), path, [&](std::string_view menuName, int32_t menuId, std::string_view description) { + //if (mPickerSearch.data() != 0 && !l::string::equal_partial(mPickerSearch.data(), menuName.data(), 0, 0, 20)) { + // return; + //} + if (!menuName.empty()) { + if (ImGui::MenuItem(menuName.data())) { + ImVec2 p = ImVec2(mUIInput.mCurPos.x - GetPosition().x, mUIInput.mCurPos.y - GetPosition().y); + p.x -= mUIRoot->GetPosition().x; + p.y -= mUIRoot->GetPosition().y; + p.x /= mUIRoot->GetScale(); + p.y /= mUIRoot->GetScale(); + p.x -= 3.0f; + p.y -= 3.0f; + auto nodeId = mNGSchema->NewNode(menuId); + auto node = mNGSchema->GetNode(nodeId); + if (node != nullptr) { + auto uiNode = l::ui::CreateUINode(mUIManager, *node, p); + mUIRoot->Add(uiNode); + + auto& uiData = node->GetUIData(); + auto position = uiNode->GetPosition(); + auto size = uiNode->GetSize(); + uiData.x = position.x; + uiData.y = position.y; + uiData.w = size.x; + uiData.h = size.y; + } + } + if (!description.empty() && ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(350); + ImGui::PushStyleColor(0, ImGui::ColorConvertU32ToFloat4(ImGui::Spectrum::GRAY900)); + ImGui::TextWrapped("%s", menuName.data()); + ImGui::PopStyleColor(); + ImGui::Separator(); + ImGui::PushStyleColor(0, ImGui::ColorConvertU32ToFloat4(ImGui::Spectrum::GRAY700)); + ImGui::TextWrapped("%s", description.data()); + ImGui::PopStyleColor(); + ImGui::EndTooltip(); } } @@ -182,13 +207,13 @@ namespace l::ui { return inputNode->ClearInput(static_cast(inputChannel)); }); - mEditVisitor.SetEditHandler([&](int32_t nodeId, int8_t channelId, float, float dy) { + mTouchEditVisitor.SetEditHandler([&](int32_t nodeId, int8_t channelId, float, float dy) { if (mNGSchema == nullptr) { return; } auto node = mNGSchema->GetNode(nodeId); - if (node->IsInputDataEditable(channelId)) { + if (node->IsInputDataEditable(channelId) && !node->IsInputDataText(channelId)) { float* nodeValue = nullptr; if (channelId < node->GetNumInputs()) { nodeValue = &node->GetInput(channelId, 1); @@ -197,7 +222,7 @@ namespace l::ui { nodeValue = &node->GetOutput(channelId, 1); } if (nodeValue != nullptr) { - if (!ImGui::IsKeyDown(ImGuiKey::ImGuiKey_LeftShift)) { + if (!ImGui::IsKeyDown(ImGuiKey::ImGuiKey_LeftAlt)) { if (!ImGui::IsKeyDown(ImGuiKey::ImGuiKey_LeftCtrl)) { *nodeValue -= dy / 100.0f; } @@ -223,6 +248,34 @@ namespace l::ui { } }); + mTextEditVisitor.SetEditHandler([&](int32_t nodeId, int8_t channelId, std::string& text, bool noedit) { + if (mNGSchema == nullptr) { + return; + } + + auto node = mNGSchema->GetNode(nodeId); + if (noedit && node->IsInputDataEditable(channelId) && node->IsInputDataText(channelId)) { + if (channelId < node->GetNumInputs()) { + text = node->GetInputText(channelId); + } + else if (channelId < node->GetNumOutputs()) { + text = node->GetOutputText(channelId); + } + } + + ImGuiIO& io = ImGui::GetIO(); + for (int i = 0; i < io.InputQueueCharacters.Size; i++) { + ImWchar c = io.InputQueueCharacters[i]; + if (text.size() <= 16) { + text += static_cast(c); + } + } + if (!noedit && channelId < node->GetNumInputs()) { + node->SetInput(channelId, text); + } + + }); + mSelectVisitor.SetDeleteHandler([&](int32_t containerId, int32_t nodeId) { if (mNGSchema == nullptr) { return; @@ -243,7 +296,7 @@ namespace l::ui { if (mNGSchema == nullptr) { return; } - //LOG(LogInfo) << "Container " << containerId << " moved to " << x << ", " << y; + //LLOG(LogInfo) << "Container " << containerId << " moved to " << x << ", " << y; auto node = mNGSchema->GetNode(nodeId); if (node != nullptr) { auto& uiData = node->GetUIData(); @@ -256,7 +309,7 @@ namespace l::ui { if (mNGSchema == nullptr) { return; } - //LOG(LogInfo) << "Container " << containerId << " resized to " << w << ", " << h; + //LLOG(LogInfo) << "Container " << containerId << " resized to " << w << ", " << h; auto node = mNGSchema->GetNode(nodeId); if (node != nullptr) { auto& uiData = node->GetUIData(); @@ -293,7 +346,8 @@ namespace l::ui { mDragVisitor.Reset(); mMoveVisitor.Reset(); mResizeVisitor.Reset(); - mEditVisitor.Reset(); + mTouchEditVisitor.Reset(); + mTextEditVisitor.Reset(); if (mUIRoot.IsValid()) { mUIRoot->RemoveAll(); @@ -321,13 +375,10 @@ namespace l::ui { if (node != nullptr) { auto& uiData = node->GetUIData(); auto p = ImVec2(uiData.x, uiData.y); - //auto s = ImVec2(uiData.w, uiData.h); - auto uiNode = l::ui::CreateUINode(mUIManager, *node, p); - //if (s.x > 10.0f && s.y > 10.0f) { - // uiNode->SetSize(s); - //} + auto s = ImVec2(uiData.w, uiData.h); + auto uiNode = l::ui::CreateUINode(mUIManager, *node, p, s); - //LOG(LogInfo) << "Replicated node type " << node->GetTypeId() << " as a ui node"; + //LLOG(LogInfo) << "Replicated node type " << node->GetTypeId() << " as a ui node"; mUIRoot->Add(uiNode); } @@ -391,7 +442,9 @@ namespace l::ui { if (IsHovered()) { if (mUIRoot->Accept(mLinkIOVisitor, mUIInput, l::ui::UITraversalMode::DFS)) { } - else if (mUIRoot->Accept(mEditVisitor, mUIInput, l::ui::UITraversalMode::DFS)) { + else if (mUIRoot->Accept(mTouchEditVisitor, mUIInput, l::ui::UITraversalMode::DFS)) { + } + else if (mUIRoot->Accept(mTextEditVisitor, mUIInput, l::ui::UITraversalMode::DFS)) { } else if (mUIRoot->Accept(mSelectVisitor, mUIInput, l::ui::UITraversalMode::BFS)) { } diff --git a/packages/rendering/source/common/ui/UIVisitors.cpp b/packages/rendering/source/common/ui/UIVisitors.cpp index 2470ccce..6b23ce4a 100644 --- a/packages/rendering/source/common/ui/UIVisitors.cpp +++ b/packages/rendering/source/common/ui/UIVisitors.cpp @@ -1,5 +1,4 @@ #include "rendering/ui/UIVisitors.h" -#include "hid/KeyboardPiano.h" namespace l::ui { @@ -245,8 +244,8 @@ namespace l::ui { } /***********************************************************************************/ - bool UIEdit::Visit(UIContainer& container, const InputState& input) { - if (!container.HasConfigFlag(UIContainer_EditFlag)) { + bool UITouchEdit::Visit(UIContainer& container, const InputState& input) { + if (!container.HasConfigFlag(UIContainer_TouchEditFlag)) { return false; } if (input.mStarted && !mEditing) { @@ -254,6 +253,7 @@ namespace l::ui { if (Overlap(input.GetLocalPos(), container.GetPosition(), container.GetPositionAtSize(), layoutArea)) { mEditing = true; mSourceContainer = &container; + mSourceContainer->SetNotification(UIContainer_TouchEditFlag); } } if (mEditing && mSourceContainer == &container) { @@ -265,6 +265,7 @@ namespace l::ui { } if (input.mStopped) { + mSourceContainer->ClearNotification(UIContainer_TouchEditFlag); mEditing = false; mSourceContainer = nullptr; } @@ -273,7 +274,54 @@ namespace l::ui { return false; } - void UIEdit::Reset() { + void UITouchEdit::Reset() { + if (mSourceContainer) { + mSourceContainer->ClearNotification(UIContainer_TouchEditFlag); + } + mEditing = false; + mSourceContainer = nullptr; + } + + /***********************************************************************************/ + bool UITextEdit::Visit(UIContainer& container, const InputState& input) { + if (!container.HasConfigFlag(UIContainer_TextEditFlag)) { + return false; + } + if (input.mStarted && !mEditing) { + auto& layoutArea = container.GetLayoutArea(); + if (Overlap(input.GetLocalPos(), container.GetPosition(), container.GetPositionAtSize(), layoutArea)) { + mEditing = true; + mSourceContainer = &container; + mSourceContainer->SetNotification(UIContainer_TextEditFlag); + if (mEditHandler) { + mEditHandler(container.GetNodeId(), static_cast(container.GetChannelId()), mEditedText, true); + } + } + } + if (mEditing && mSourceContainer == &container) { + if (ImGui::IsKeyPressed(ImGuiKey::ImGuiKey_Backspace, true)) { + if (!mEditedText.empty()) { + mEditedText.pop_back(); + } + } + if (mEditHandler) { + mEditHandler(container.GetNodeId(), static_cast(container.GetChannelId()), mEditedText, false); + } + if (ImGui::IsKeyPressed(ImGuiKey::ImGuiKey_Enter, false) || ImGui::IsKeyPressed(ImGuiKey::ImGuiKey_Escape, false)) { + mSourceContainer->ClearNotification(UIContainer_TextEditFlag); + mEditing = false; + mSourceContainer = nullptr; + mEditedText.clear(); + } + return mEditing; + } + return false; + } + + void UITextEdit::Reset() { + if (mSourceContainer) { + mSourceContainer->ClearNotification(UIContainer_TextEditFlag); + } mEditing = false; mSourceContainer = nullptr; } @@ -284,6 +332,15 @@ namespace l::ui { return false; } + // Filter based on draw mode + bool isLink = container.HasConfigFlag(UIContainer_LinkFlag); + if (mDrawMode == UIDrawMode::LinksOnly && !isLink) { + return false; + } + if (mDrawMode == UIDrawMode::NoLinks && isLink) { + return false; + } + auto& layoutArea = container.GetLayoutArea(); float splineThickness = 2.0f; @@ -303,6 +360,24 @@ namespace l::ui { const char* nameEnd; auto renderType = container.GetRenderData().mType; + + + switch (container.GetRenderData().mType) { + case l::ui::UIRenderType::Rect: + case l::ui::UIRenderType::RectFilled: + case l::ui::UIRenderType::Texture: + case l::ui::UIRenderType::LinkH: + case l::ui::UIRenderType::NodeOutputValue: + if (container.HasConfigFlag(UIContainer_SelectFlag) && container.HasNotification(UIContainer_SelectFlag)) { + auto p1cpy = ImVec2(p1.x - 1.0f, p1.y - 1.0f); + auto p2cpy = ImVec2(p2.x + 1.0f, p2.y + 1.0f); + mDrawList->AddRect(p1cpy, p2cpy, mSelectColor, 0.0f, 0, 3.0f * container.GetScale() * layoutArea.mScale); + } + break; + default: + break; + } + switch (renderType) { case l::ui::UIRenderType::Rect: mDrawList->AddRect(p1, p2, color, 5.0f, ImDrawFlags_RoundCornersAll, 1.0f * container.GetScale() * layoutArea.mScale); @@ -405,21 +480,30 @@ namespace l::ui { case l::ui::UIRenderType::RectFilled: case l::ui::UIRenderType::Texture: case l::ui::UIRenderType::LinkH: - if (container.HasConfigFlag(UIContainer_SelectFlag) && container.HasNotification(UIContainer_SelectFlag)) { + case l::ui::UIRenderType::NodeOutputValue: + if (container.HasConfigFlag(UIContainer_TouchEditFlag) && container.HasNotification(UIContainer_TouchEditFlag)) { + auto p1cpy = ImVec2(p1.x - 1.0f, p1.y - 1.0f); + auto p2cpy = ImVec2(p2.x + 1.0f, p2.y + 1.0f); + mDrawList->AddRect(p1cpy, p2cpy, mSelectColor, 0.0f, 0, 1.0f * container.GetScale() * layoutArea.mScale); + } + if (container.HasConfigFlag(UIContainer_TextEditFlag) && container.HasNotification(UIContainer_TextEditFlag)) { auto p1cpy = ImVec2(p1.x - 1.0f, p1.y - 1.0f); auto p2cpy = ImVec2(p2.x + 1.0f, p2.y + 1.0f); - mDrawList->AddRect(p1cpy, p2cpy, mSelectColor, 0.0f, 0, 1.0f); + mDrawList->AddRect(p1cpy, p2cpy, mSelectColor, 0.0f, 0, 1.0f * container.GetScale() * layoutArea.mScale); } if (container.HasConfigFlag(ui::UIContainer_ResizeFlag)) { float size = 3.0f * layoutArea.mScale; ImVec2 p3 = layoutArea.Transform(pLowRight, ImVec2(-size, -size)); - ImVec2 p4 = layoutArea.Transform(pLowRight, ImVec2(size, size)); + ImVec2 p4 = layoutArea.Transform(pLowRight, ImVec2(size - 2, size - 2)); if (container.HasNotification(ui::UIContainer_ResizeFlag)) { float size2 = 5.0f * layoutArea.mScale; p3 = layoutArea.Transform(pLowRight, ImVec2(-size2, -size2)); - p4 = layoutArea.Transform(pLowRight, ImVec2(size2, size2)); + p4 = layoutArea.Transform(pLowRight, ImVec2(size2 - 1, size2 - 1)); + mDrawList->AddRectFilled(p3, p4, mSelectColor); + } + else { + mDrawList->AddRectFilled(p3, p4, color); } - mDrawList->AddRectFilled(p3, p4, color); } break; default: @@ -449,11 +533,11 @@ namespace l::ui { // * input container co-parent -> link container // But a link container is still owned by only one container, the output container - { + if (container.HasConfigFlag(UIContainer_OutputFlag)) { // output node checks auto& outputContainer = container; // Create a link connection and attach it at a source node - if (outputContainer.HasConfigFlag(UIContainer_OutputFlag) && !mDragging && input.mStarted && mLinkContainer.Get() == nullptr) { + if (!mDragging && input.mStarted && mLinkContainer.Get() == nullptr) { ImVec2 pCenter = outputContainer.GetPosition(); ImVec2 size = outputContainer.GetSize(); auto& layoutArea = outputContainer.GetLayoutArea(); @@ -468,25 +552,27 @@ namespace l::ui { } } } - - { + else if (container.HasConfigFlag(UIContainer_LinkFlag)) { auto& linkContainer = container; // Detach a link connection from a destination node with an existing link connection - if (linkContainer.HasConfigFlag(UIContainer_LinkFlag) && !mDragging && input.mStarted && mLinkContainer.Get() == nullptr && linkContainer.GetCoParent() != nullptr) { + if (!mDragging && input.mStarted && mLinkContainer.Get() == nullptr && linkContainer.GetCoParent() != nullptr) { ImVec2 pCenter = linkContainer.GetCoParent()->GetPosition(); ImVec2 size = linkContainer.GetCoParent()->GetSize(); ImVec2 pT = linkContainer.GetCoParent()->GetLayoutArea().Transform(pCenter); if (OverlapCircle(input.mCurPos, pT, 2.0f * size.x * linkContainer.GetCoParent()->GetLayoutArea().mScale)) { mLinkContainer.mContainer = &linkContainer; mLinkHandler(mLinkContainer->GetCoParent()->GetNodeId(), mLinkContainer->GetParent()->GetNodeId(), mLinkContainer->GetCoParent()->GetChannelId(), mLinkContainer->GetParent()->GetChannelId(), false); + if (linkContainer.GetCoParent()) { + linkContainer.GetCoParent()->SetCoParent(nullptr); + } mDragging = true; return true; } } // Drag the link end - if (mDragging && mLinkContainer.Get() != nullptr && linkContainer.HasConfigFlag(UIContainer_LinkFlag) && mLinkContainer.Get() == &linkContainer) { + if (mDragging && mLinkContainer.Get() != nullptr && mLinkContainer.Get() == &linkContainer) { // On the newly created link container, drag the end point along the mouse movement auto& layoutArea = mLinkContainer->GetLayoutArea(); @@ -494,34 +580,37 @@ namespace l::ui { mLinkContainer->Move(move); } } - - { + else if (container.HasConfigFlag(UIContainer_InputFlag)) { auto& inputContainer = container; // Check containers with input flags, i.e. a node input channel area - if (mDragging && mLinkContainer.Get() != nullptr && inputContainer.HasConfigFlag(UIContainer_InputFlag)) { + if (mDragging && mLinkContainer.Get() != nullptr) { ImVec2 pCenter = inputContainer.GetPosition(); ImVec2 size = inputContainer.GetSize(); auto& layoutArea = inputContainer.GetLayoutArea(); ImVec2 pT = layoutArea.Transform(pCenter); - // if there is overlap we connect it + // we're dragging, so if there is overlap we connect it if (OverlapCircle(input.mCurPos, pT, 2.0f * size.x * layoutArea.mScale)) { - if (mLinkHandler(inputContainer.GetNodeId(), mLinkContainer->GetParent()->GetNodeId(), inputContainer.GetChannelId(), mLinkContainer->GetParent()->GetChannelId(), true)) { + if (mLinkContainer->GetCoParent() == nullptr && mLinkHandler(inputContainer.GetNodeId(), mLinkContainer->GetParent()->GetNodeId(), inputContainer.GetChannelId(), mLinkContainer->GetParent()->GetChannelId(), true)) { mLinkContainer->SetNotification(UIContainer_LinkFlag); mLinkContainer->SetCoParent(&inputContainer); inputContainer.SetCoParent(mLinkContainer.Get()); + //LLOG(LogInfo) << "Connected to " << inputContainer.GetChannelId(); } else { + //LLOG(LogInfo) << "Already connected"; // This link is already connected (or there is another link connected already) } } - // If this link if connected to this input node channel area, we detach it because the overlap failed (we moved it away) + // We're dragging, so if the overlap fail and this link is connected, we detach it (we moved it away) else if (mLinkContainer->GetCoParent() == &inputContainer) { mLinkHandler(inputContainer.GetNodeId(), mLinkContainer->GetParent()->GetNodeId(), inputContainer.GetChannelId(), mLinkContainer->GetParent()->GetChannelId(), false); - mLinkContainer->SetCoParent(nullptr); mLinkContainer->ClearNotification(UIContainer_LinkFlag); + mLinkContainer->SetCoParent(nullptr); + inputContainer.SetCoParent(nullptr); + //LLOG(LogInfo) << "Disconnected from " << inputContainer.GetChannelId(); } if (input.mStopped) { diff --git a/packages/serialization/CMakeLists.txt b/packages/serialization/CMakeLists.txt index 1ed2f61a..5a26a78a 100644 --- a/packages/serialization/CMakeLists.txt +++ b/packages/serialization/CMakeLists.txt @@ -3,6 +3,8 @@ project (serialization) set(deps logging testing + + math ) bs_generate_package(serialization "tier1" "${deps}" "various;jsonxx") diff --git a/packages/serialization/include/serialization/JsonParser.h b/packages/serialization/include/serialization/JsonParser.h index 2150f47b..79c30b66 100644 --- a/packages/serialization/include/serialization/JsonParser.h +++ b/packages/serialization/include/serialization/JsonParser.h @@ -1,6 +1,7 @@ #pragma once #include +#include #define JSMN_HEADER #include @@ -52,6 +53,7 @@ namespace l::serialization { bool as_bool() const; double as_double() const; float as_float() const; + l::math::fp::FixedPoint as_fixed_point() const; int8_t as_int8() const; int16_t as_int16() const; int32_t as_int32() const; @@ -146,16 +148,16 @@ namespace l::serialization { mTokenCount = 0; switch (ret) { case JSMN_ERROR_INVAL: - LOG(LogError) << "Failure to parse json value"; + LLOG(LogError) << "Failure to parse json value"; return { false, ret }; case JSMN_ERROR_NOMEM: - LOG(LogError) << "Token buffer is to small"; + LLOG(LogError) << "Token buffer is to small"; return { false, ret }; case JSMN_ERROR_PART: - //LOG(LogInfo) << "Json data is not completed"; + //LLOG(LogInfo) << "Json data is not completed"; return { false, ret }; default: - LOG(LogError) << "Unknown error"; + LLOG(LogError) << "Unknown error"; return { false, -4 }; } } diff --git a/packages/serialization/source/common/JsonParser.cpp b/packages/serialization/source/common/JsonParser.cpp index c4bec9a0..8392a71c 100644 --- a/packages/serialization/source/common/JsonParser.cpp +++ b/packages/serialization/source/common/JsonParser.cpp @@ -2,9 +2,10 @@ #include #include - #include +#include + namespace l::serialization { bool JsonValue::valid() const { return mTokens && mCount > 0; } @@ -28,11 +29,15 @@ namespace l::serialization { } double JsonValue::as_double() const { - return std::atof(as_string().data()); + return std::stod(as_string().data()); } float JsonValue::as_float() const { - return static_cast(std::atof(as_string().data())); + return static_cast(std::stod(as_string().data())); + } + + l::math::fp::FixedPoint JsonValue::as_fixed_point() const { + return l::math::fp::FixedPoint(as_string()); } int8_t JsonValue::as_int8() const { diff --git a/packages/serialization/source/common/SerializationBase.cpp b/packages/serialization/source/common/SerializationBase.cpp index 872d7431..cad7853f 100644 --- a/packages/serialization/source/common/SerializationBase.cpp +++ b/packages/serialization/source/common/SerializationBase.cpp @@ -103,11 +103,11 @@ namespace l::serialization { bool peekSuccessful = headerValidity.Peek(data); if (mExpectIdentifier) { if (!peekSuccessful || !headerValidity.IsIdentifierValid()) { - LOG(LogError) << "Expected serialization identifier: " << headerValidity.mIdentifier << " (version: " << headerValidity.mVersion << ")"; + LLOG(LogError) << "Expected serialization identifier: " << headerValidity.mIdentifier << " (version: " << headerValidity.mVersion << ")"; return; } if (!peekSuccessful || !headerValidity.IsVersionValid(mLatestVersion)) { - LOG(LogError) << "Expected serialization version: " << headerValidity.mVersion << " (identifier: " << headerValidity.mIdentifier << ")"; + LLOG(LogError) << "Expected serialization version: " << headerValidity.mVersion << " (identifier: " << headerValidity.mIdentifier << ")"; return; } mUseIdentifier = true; // if identifier is expected, we should continue using it even if user didn't not set it @@ -120,7 +120,7 @@ namespace l::serialization { mExpectIdentifier = true; if (!headerValidity.IsVersionValid(mLatestVersion)) { - LOG(LogError) << "Expected serialization version: " << headerValidity.mVersion; + LLOG(LogError) << "Expected serialization version: " << headerValidity.mVersion; return; } diff --git a/packages/serialization/tests/common/Base16Test.cpp b/packages/serialization/tests/common/Base16Test.cpp index a0c3643f..8c30c482 100644 --- a/packages/serialization/tests/common/Base16Test.cpp +++ b/packages/serialization/tests/common/Base16Test.cpp @@ -7,9 +7,9 @@ TEST(Base16, Basic) { auto message = "test#!%14+,&) { return false; }; diff --git a/packages/storage/include/storage/FileCacheProvider.h b/packages/storage/include/storage/FileCacheProvider.h index cea7908f..8fb4e941 100644 --- a/packages/storage/include/storage/FileCacheProvider.h +++ b/packages/storage/include/storage/FileCacheProvider.h @@ -22,6 +22,7 @@ namespace l::filecache { mExtension(extension) {} ~FileCacheProvider() = default; + virtual bool UnPersistData(std::string_view path) override; virtual bool PersistData(std::string_view path, const std::vector& data) override; virtual bool ProvideData(std::string_view path, std::vector& data) override; diff --git a/packages/storage/include/storage/SequentialCache.h b/packages/storage/include/storage/SequentialCache.h index df609596..930b0e79 100644 --- a/packages/storage/include/storage/SequentialCache.h +++ b/packages/storage/include/storage/SequentialCache.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "logging/LoggingAll.h" #include "math/MathConstants.h" @@ -25,10 +26,20 @@ namespace l::filecache { int32_t GetClampedPositionOffset(int32_t position, int32_t blockWidth); int32_t GetClampedPositionOffsetFromIndex(int32_t index, int32_t blockWidth, int32_t numBlockEntries); - std::string GetCacheBlockName( - std::string_view prefix, - int32_t blockWidth, - int32_t clampedPos); + template + l::string::string_buffer CreateCacheBlockName( + std::string_view prefix, + int32_t blockWidth, + int32_t clampedPos) { + l::string::string_buffer key; + key.append(prefix); + key.append("_"); + key.printf("%i", blockWidth); + key.append("_"); + key.printf("%i", clampedPos); + return key; + } + template class CacheBlock { @@ -62,11 +73,33 @@ namespace l::filecache { archive(*self.mData.get()); } - bool PersistData() { + void PersistOnDestruction() { + mPersistOnDestruction = true; + } + + bool WillPersistOnDestruction() { + return mPersistOnDestruction; + } + + void NoPersistOnDestruction() { + mPersistOnDestruction = false; + } + + bool UnpersistData() { if (!mCacheProvider) { return false; } + std::lock_guard lock(mPathMutex); + mCacheProvider->UnPersistData(mPath); + return true; + } + + bool PersistData() { + if (!mCacheProvider) { + return false; + } + std::vector data; GetArchiveData(data); @@ -143,11 +176,30 @@ namespace l::filecache { } } + void Deallocate() { + std::lock_guard lock(mDataMutex); + if (!mData) { + mData = nullptr; + } + } + l::concurrency::ObjectLock Get() { mDataMutex.lock(); return l::concurrency::ObjectLock(mDataMutex, mData.get()); } + void ClearFlags(uint32_t flags) { + mFlags &= ~flags; + } + + void SetFlags(uint32_t flags) { + mFlags |= flags; + } + + bool HasFlags(uint32_t flags) { + return (mFlags.load() & flags) == flags; + } + protected: std::mutex mDataMutex; std::unique_ptr mData; @@ -156,7 +208,8 @@ namespace l::filecache { std::mutex mPathMutex; ICacheProvider* mCacheProvider; - bool mPersistOnDestruction; + std::atomic_bool mPersistOnDestruction; + std::atomic_uint32_t mFlags = 0; }; template @@ -176,6 +229,40 @@ namespace l::filecache { } ~SequentialCache() = default; + bool HasAny(int32_t startposition, int32_t endposition) { + auto clampedStartPos = GetClampedPosition(startposition, mCacheBlockWidth); + auto clampedEndPos = GetClampedPosition(endposition, mCacheBlockWidth); + + std::lock_guard lock(mMutexCacheBlockMap); + + if (clampedStartPos < clampedEndPos) { + do { + auto it = mCacheBlockMap.find(clampedStartPos); + if (it != mCacheBlockMap.end()) { + return true; + } + if (static_cast(clampedStartPos) + mCacheBlockWidth >= l::math::constants::INTMAX) { + break; + } + clampedStartPos += mCacheBlockWidth; + } while (clampedStartPos <= clampedEndPos); + } + else { + do { + auto it = mCacheBlockMap.find(clampedStartPos); + if (it != mCacheBlockMap.end()) { + return true; + } + if (static_cast(clampedStartPos) - mCacheBlockWidth <= l::math::constants::INTMIN) { + break; + } + clampedStartPos -= mCacheBlockWidth; + } while (clampedStartPos >= clampedEndPos); + } + + return false; + } + bool Has(int32_t position) { auto clampedPos = GetClampedPosition(position, mCacheBlockWidth); @@ -193,8 +280,8 @@ namespace l::filecache { std::lock_guard lock(mMutexCacheBlockMap); auto it = mCacheBlockMap.find(clampedPos); if (it == mCacheBlockMap.end()) { - auto filename = GetCacheBlockName(mCacheKey, mCacheBlockWidth, clampedPos); - mCacheBlockMap.emplace(clampedPos, std::make_unique>(filename, mCacheProvider, noProvisioning)); + auto filename = CreateCacheBlockName(mCacheKey, mCacheBlockWidth, clampedPos); + mCacheBlockMap.emplace(clampedPos, std::make_unique>(filename.str(), mCacheProvider, noProvisioning)); it = mCacheBlockMap.find(clampedPos); } @@ -222,6 +309,18 @@ namespace l::filecache { {} ~SequentialCacheStore() = default; + bool HasAny(std::string_view cacheKey, int32_t startposition, int32_t endposition) { + std::unique_lock lock(mMutexSequentialCacheMap); + auto it = mSequentialCacheMap.find(cacheKey.data()); + if (it == mSequentialCacheMap.end()) { + return false; + } + SequentialCache* sequentialCacheMap = it->second.get(); + lock.unlock(); + + return sequentialCacheMap->HasAny(startposition, endposition); + } + bool Has(std::string_view cacheKey, int32_t position) { std::unique_lock lock(mMutexSequentialCacheMap); auto it = mSequentialCacheMap.find(cacheKey.data()); @@ -251,7 +350,8 @@ namespace l::filecache { int32_t beginPosition, int32_t endPosition, int32_t blockWidth, - std::function*)> callback) { + std::function*)> callback, + bool forceLoad = true) { std::unique_lock lock(mMutexSequentialCacheMap); auto it = mSequentialCacheMap.find(cacheKey.data()); @@ -273,7 +373,10 @@ namespace l::filecache { beginPosition = GetClampedPosition(beginPosition, cacheBlockWidth); if (beginPosition < endPosition) { do { - cacheBlock = sequentialCacheMap->Get(beginPosition); + cacheBlock = nullptr; + if (forceLoad || sequentialCacheMap->Has(beginPosition)) { + cacheBlock = sequentialCacheMap->Get(beginPosition); + } if (cacheBlock != nullptr) { if (!callback(beginPosition, cacheBlockWidth, cacheBlock)) { break; @@ -287,7 +390,10 @@ namespace l::filecache { } else { do { - cacheBlock = sequentialCacheMap->Get(beginPosition); + cacheBlock = nullptr; + if (forceLoad || sequentialCacheMap->Has(beginPosition)) { + cacheBlock = sequentialCacheMap->Get(beginPosition); + } if (cacheBlock != nullptr) { if (!callback(beginPosition, cacheBlockWidth, cacheBlock)) { break; diff --git a/packages/storage/source/common/FileCacheProvider.cpp b/packages/storage/source/common/FileCacheProvider.cpp index 572b67ff..17a998b6 100644 --- a/packages/storage/source/common/FileCacheProvider.cpp +++ b/packages/storage/source/common/FileCacheProvider.cpp @@ -5,6 +5,12 @@ namespace l::filecache { + bool FileCacheProvider::UnPersistData(std::string_view path) { + auto file = mLocation / (std::string(path) + mExtension); + std::filesystem::remove(file); + return true; + } + bool FileCacheProvider::PersistData(std::string_view path, const std::vector& data) { if (data.empty()) { return false; @@ -18,7 +24,7 @@ namespace l::filecache { } f.write(data); f.close(); - //LOG(LogInfo) << "Saved " << path; + //LLOG(LogInfo) << "Saved " << path; return true; } diff --git a/packages/storage/source/common/SequentialCache.cpp b/packages/storage/source/common/SequentialCache.cpp index 34c185ce..47a35ae5 100644 --- a/packages/storage/source/common/SequentialCache.cpp +++ b/packages/storage/source/common/SequentialCache.cpp @@ -15,12 +15,4 @@ namespace l::filecache { return blockWidth * index / numBlockEntries; } - std::string GetCacheBlockName(std::string_view prefix, int32_t blockWidth, int32_t clampedPos) { - std::stringstream name; - name << prefix.data(); - name << "_" << blockWidth; - name << "_" << clampedPos; - return name.str(); - } - } diff --git a/packages/storage/tests/common/SequentialCacheTest.cpp b/packages/storage/tests/common/SequentialCacheTest.cpp index eb857e21..0e2d9428 100644 --- a/packages/storage/tests/common/SequentialCacheTest.cpp +++ b/packages/storage/tests/common/SequentialCacheTest.cpp @@ -46,6 +46,10 @@ TEST(SequentialCacheStore, Setup) { TEST_TRUE(block->HasData(), ""); TEST_TRUE(block->Get()->mValue == 1, ""); + + block->UnpersistData(); + + TEST_FALSE(std::filesystem::exists("tests/store/Key_10_20.test"), ""); } return 0; } diff --git a/packages/testing/include/testing/Test.h b/packages/testing/include/testing/Test.h index 1cb0c8d1..462128e9 100644 --- a/packages/testing/include/testing/Test.h +++ b/packages/testing/include/testing/Test.h @@ -18,6 +18,7 @@ namespace testing { bool run_tests(const char* app); bool run_perfs(const char* app); + void show_perfs(const char* app); } } @@ -26,9 +27,9 @@ namespace testing { #define TESTNAME(group, name) CONCATE(Test, CONCATE(group, name)) \ #define TEST(testgroup, testname) \ - int TESTNAME(testgroup, testname)(); \ + static int TESTNAME(testgroup, testname)(); \ ADDTEST(STRINGIFY(testgroup), STRINGIFY(testname), TESTNAME(testgroup, testname)) \ - int TESTNAME(testgroup, testname)() \ + static int TESTNAME(testgroup, testname)() \ @@ -39,77 +40,77 @@ namespace testing { #define PERFNAMEWRAPPER(group, name) CONCATE(Perf, CONCATE(group, CONCATE(name, Wrapper))) \ #define PERF_TEST(perfgroup, perfname) \ - int PERFNAME(perfgroup, perfname)(); \ - int PERFNAMEWRAPPER(perfgroup, perfname)() {l::testing::get_current_test_group()=#perfgroup;return PERFNAME(perfgroup, perfname)();} \ + static int PERFNAME(perfgroup, perfname)(); \ + static int PERFNAMEWRAPPER(perfgroup, perfname)() {l::testing::get_current_test_group()=#perfgroup;return PERFNAME(perfgroup, perfname)();} \ ADDPERF(STRINGIFY(perfgroup), STRINGIFY(perfname), PERFNAMEWRAPPER(perfgroup, perfname)) \ - int PERFNAME(perfgroup, perfname)() \ + static int PERFNAME(perfgroup, perfname)() \ #ifndef _DEBUG #define TEST_TRUE(expr, msg) \ - if (!(expr)) { LOG(LogError) << msg; LOG(LogTest) << "Test failed"; return 1;} \ + if (!(expr)) { LLOG(LogError) << msg; LLOG(LogTest) << "Test failed"; return 1;} \ #define TEST_FALSE(expr, msg) \ - if (!!(expr)) { LOG(LogError) << msg; LOG(LogTest) << "Test failed"; return 1;} \ + if (!!(expr)) { LLOG(LogError) << msg; LLOG(LogTest) << "Test failed"; return 1;} \ #define TEST_EQ(expr1, expr2, msg) \ - if (expr1 != expr2) { LOG(LogError) << msg; LOG(LogTest) << "Test failed"; return 1;} \ + if (expr1 != expr2) { LLOG(LogError) << msg; LLOG(LogTest) << "Test failed"; return 1;} \ #define TEST_FUZZY(expr1, expr2, tolerance, msg) \ - if (sqrt((expr1 - expr2)*(expr1 - expr2)) > tolerance) { LOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ")" << msg; LOG(LogTest) << "Test failed: "; return 1;} \ + if (sqrt((expr1 - expr2)*(expr1 - expr2)) > tolerance) { LLOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ")" << msg; LLOG(LogTest) << "Test failed: "; return 1;} \ #define TEST_FUZZY2(expr1, expr2, msg) \ - if (sqrt((expr1 - expr2)*(expr1 - expr2)) > 0.000000001) { LOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ")" << msg; LOG(LogTest) << "Test failed: "; return 1;} \ + if (sqrt((expr1 - expr2)*(expr1 - expr2)) > 0.000000001) { LLOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ")" << msg; LLOG(LogTest) << "Test failed: "; return 1;} \ #define TEST_TRUE_NO_RET(expr, msg) \ - if (!(expr)) { LOG(LogError) << msg; LOG(LogTest) << "Test failed";} \ + if (!(expr)) { LLOG(LogError) << msg; LLOG(LogTest) << "Test failed";} \ #define TEST_FALSE_NO_RET(expr, msg) \ - if (!!(expr)) { LOG(LogError) << msg; LOG(LogTest) << "Test failed";} \ + if (!!(expr)) { LLOG(LogError) << msg; LLOG(LogTest) << "Test failed";} \ #define TEST_EQ_NO_RET(expr1, expr2, msg) \ - if (expr1 != expr2) { LOG(LogError) << msg; LOG(LogTest) << "Test failed";} \ + if (expr1 != expr2) { LLOG(LogError) << msg; LLOG(LogTest) << "Test failed";} \ #define TEST_FUZZY_NO_RET(expr1, expr2, tolerance, msg) \ - if (sqrt((expr1 - expr2)*(expr1 - expr2)) > tolerance) { LOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ")" << msg; LOG(LogTest) << "Test failed: ";} \ + if (sqrt((expr1 - expr2)*(expr1 - expr2)) > tolerance) { LLOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ")" << msg; LLOG(LogTest) << "Test failed: ";} \ #define TEST_FUZZY2_NO_RET(expr1, expr2, msg) \ - if (sqrt((expr1 - expr2)*(expr1 - expr2)) > 0.000000001) { LOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ")" << msg; LOG(LogTest) << "Test failed: ";} \ + if (sqrt((expr1 - expr2)*(expr1 - expr2)) > 0.000000001) { LLOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ")" << msg; LLOG(LogTest) << "Test failed: ";} \ #else #define TEST_TRUE(expr, msg) \ - if (!(expr)) { LOG(LogError) << "TEST_TRUE(" << std::to_string(expr) << ") " << msg; LOG(LogTest) << "Test failed"; DEBUG_BREAK;return 1;} \ + if (!(expr)) { LLOG(LogError) << "TEST_TRUE(" << std::to_string(expr) << ") " << msg; LLOG(LogTest) << "Test failed"; DEBUG_BREAK;return 1;} \ #define TEST_FALSE(expr, msg) \ - if (!!(expr)) { LOG(LogError) << "TEST_FALSE(" << std::to_string(expr) << ") " << msg; LOG(LogTest) << "Test failed"; DEBUG_BREAK;return 1;} \ + if (!!(expr)) { LLOG(LogError) << "TEST_FALSE(" << std::to_string(expr) << ") " << msg; LLOG(LogTest) << "Test failed"; DEBUG_BREAK;return 1;} \ #define TEST_EQ(expr1, expr2, msg) \ - if (expr1 != expr2) { LOG(LogError) << "TEST_EQ(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ") " << msg; LOG(LogTest) << "Test failed"; DEBUG_BREAK;return 1;} \ + if (expr1 != expr2) { LLOG(LogError) << "TEST_EQ(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ") " << msg; LLOG(LogTest) << "Test failed"; DEBUG_BREAK;return 1;} \ #define TEST_FUZZY(expr1, expr2, tolerance, msg) \ - if (sqrt((expr1 - expr2)*(expr1 - expr2)) > tolerance) { LOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ") " << msg; LOG(LogTest) << "Test failed: "; DEBUG_BREAK;return 1;} \ + if (sqrt((expr1 - expr2)*(expr1 - expr2)) > tolerance) { LLOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ") " << msg; LLOG(LogTest) << "Test failed: "; DEBUG_BREAK;return 1;} \ #define TEST_FUZZY2(expr1, expr2, msg) \ - if (sqrt((expr1 - expr2)*(expr1 - expr2)) > 0.000000001) { LOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ") " << msg; LOG(LogTest) << "Test failed: "; DEBUG_BREAK;return 1;} \ + if (sqrt((expr1 - expr2)*(expr1 - expr2)) > 0.000000001) { LLOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ") " << msg; LLOG(LogTest) << "Test failed: "; DEBUG_BREAK;return 1;} \ #define TEST_TRUE_NO_RET(expr, msg) \ - if (!(expr)) { LOG(LogError) << "TEST_TRUE(" << std::to_string(expr) << ") " << msg; LOG(LogTest) << "Test failed"; DEBUG_BREAK;} \ + if (!(expr)) { LLOG(LogError) << "TEST_TRUE(" << std::to_string(expr) << ") " << msg; LLOG(LogTest) << "Test failed"; DEBUG_BREAK;} \ #define TEST_FALSE_NO_RET(expr, msg) \ - if (!!(expr)) { LOG(LogError) << "TEST_FALSE(" << std::to_string(expr) << ") " << msg; LOG(LogTest) << "Test failed"; DEBUG_BREAK;} \ + if (!!(expr)) { LLOG(LogError) << "TEST_FALSE(" << std::to_string(expr) << ") " << msg; LLOG(LogTest) << "Test failed"; DEBUG_BREAK;} \ #define TEST_EQ_NO_RET(expr1, expr2, msg) \ - if (expr1 != expr2) { LOG(LogError) << "TEST_EQ(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ") " << msg; LOG(LogTest) << "Test failed"; DEBUG_BREAK;} \ + if (expr1 != expr2) { LLOG(LogError) << "TEST_EQ(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ") " << msg; LLOG(LogTest) << "Test failed"; DEBUG_BREAK;} \ #define TEST_FUZZY_NO_RET(expr1, expr2, tolerance, msg) \ - if (sqrt((expr1 - expr2)*(expr1 - expr2)) > tolerance) { LOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ") " << msg; LOG(LogTest) << "Test failed: "; DEBUG_BREAK;} \ + if (sqrt((expr1 - expr2)*(expr1 - expr2)) > tolerance) { LLOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ") " << msg; LLOG(LogTest) << "Test failed: "; DEBUG_BREAK;} \ #define TEST_FUZZY2_NO_RET(expr1, expr2, msg) \ - if (sqrt((expr1 - expr2)*(expr1 - expr2)) > 0.000000001) { LOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ") " << msg; LOG(LogTest) << "Test failed: "; DEBUG_BREAK;} \ + if (sqrt((expr1 - expr2)*(expr1 - expr2)) > 0.000000001) { LLOG(LogError) << "TEST_FUZZY(" << std::to_string(expr1) << ", " << std::to_string(expr2) << ") " << msg; LLOG(LogTest) << "Test failed: "; DEBUG_BREAK;} \ #endif #define TEST_RUN(app) \ - if (!l::testing::run_tests(app)) { LOG(LogTest) << "Test run failed"; return 1;} \ - if (!l::testing::run_perfs(app)) { LOG(LogTest) << "Perf run failed"; return 1;} + if (!l::testing::run_tests(app)) { LLOG(LogTest) << "Test run failed"; return 1;} \ + if (!l::testing::run_perfs(app)) { LLOG(LogTest) << "Perf run failed"; return 1;} diff --git a/packages/testing/include/testing/Timer.h b/packages/testing/include/testing/Timer.h index 3586f1d4..2a924280 100644 --- a/packages/testing/include/testing/Timer.h +++ b/packages/testing/include/testing/Timer.h @@ -24,6 +24,8 @@ namespace l::testing { struct TimeMeasure { double mSeconds = 0; + double mMin = 100000; + double mMax = 0.0f; uint64_t mCount = 0; }; @@ -38,6 +40,7 @@ namespace l::testing { std::map& get_time_measures(std::string_view groupName); TimeMeasure& get_time_measure(std::string_view groupName, std::string_view id); + void show_measurements(const std::string& group); } #define PERF_TIMER(name) auto UNIQUE(PerfTimer) = std::make_unique(l::testing::get_current_test_group(), name) diff --git a/packages/testing/source/common/Test.cpp b/packages/testing/source/common/Test.cpp index 027157c6..20832d8b 100644 --- a/packages/testing/source/common/Test.cpp +++ b/packages/testing/source/common/Test.cpp @@ -56,7 +56,7 @@ namespace testing { } bool run_tests(const char* app) { - LOG(LogTitle) << "Unit tests " << app; + LLOG(LogTitle) << "Unit tests " << app; bool test_success = true; { @@ -67,9 +67,9 @@ namespace testing { auto& groups = get_test_groups(); for (auto& groupIt : groups) { size_t total = groupIt.second->size(); - LOG(LogTitle) << "# " << groupIt.first; + LLOG(LogTitle) << "# " << groupIt.first; for (auto& f : *groupIt.second) { - LOG(LogTitle) << "## " << groupIt.first + "::" + f.first; + LLOG(LogTitle) << "## " << groupIt.first + "::" + f.first; if (f.second()) { failed_tests++; test_success = false; @@ -77,17 +77,17 @@ namespace testing { } std::ostringstream msg; msg << "## Test result for '" + groupIt.first + "': successful tests(" << (total - failed_tests) << " / " << (total) << ")"; - LOG(LogTitle) << msg.str(); + LLOG(LogTitle) << msg.str(); summary.push_back(msg.str()); } - LOG(LogTitle) << "## Test summary "; + LLOG(LogTitle) << "## Test summary "; for (auto& str : summary) { - LOG(LogTitle) << str; + LLOG(LogTitle) << str; } if (!test_success) { - LOG(LogTitle) << "All tests did not go through successfully.."; + LLOG(LogTitle) << "All tests did not go through successfully.."; } } @@ -95,7 +95,7 @@ namespace testing { } bool run_perfs(const char* app) { - LOG(LogTitle) << "Performance tests " << app; + LLOG(LogTitle) << "Performance tests " << app; bool perf_success = true; { @@ -106,9 +106,9 @@ namespace testing { auto& groups = get_perf_groups(); for (auto& groupIt : groups) { size_t total = groupIt.second->size(); - LOG(LogTitle) << "## " << groupIt.first; + LLOG(LogTitle) << "## " << groupIt.first; for (auto& f : *groupIt.second) { - LOG(LogTitle) << groupIt.first + "::" + f.first; + LLOG(LogTitle) << groupIt.first + "::" + f.first; if (f.second()) { failed_perfs++; perf_success = false; @@ -117,27 +117,57 @@ namespace testing { auto& measures = get_time_measures(groupIt.first); for (auto& result : measures) { - LOG(LogInfo) << groupIt.first << "::" << result.first << ": " << result.second.mSeconds << " sec."; + LLOG(LogInfo) << groupIt.first << "::" << result.first << ": " << result.second.mSeconds << " sec."; } } std::ostringstream msg; msg << "Performance result for '" + groupIt.first + "': successful perfs(" << (total - failed_perfs) << " / " << (total) << ")"; - LOG(LogTitle) << msg.str(); + LLOG(LogTitle) << msg.str(); summary.push_back(msg.str()); } - LOG(LogTitle) << "----"; + LLOG(LogTitle) << "----"; for (auto& str : summary) { - LOG(LogTitle) << str; + LLOG(LogTitle) << str; } if (!perf_success) { - LOG(LogTitle) << "All perfs did not go through successfully.."; + LLOG(LogTitle) << "All perfs did not go through successfully.."; } } return perf_success; } + + void show_perfs(const char* app) { + LLOG(LogTitle) << "Performance tests " << app; + + std::vector summary; + + auto& groups = get_perf_groups(); + for (auto& groupIt : groups) { + LLOG(LogTitle) << "## " << groupIt.first; + for (auto& f : *groupIt.second) { + LLOG(LogTitle) << groupIt.first + "::" + f.first; + auto& measures = get_time_measures(groupIt.first); + + for (auto& result : measures) { + LLOG(LogInfo) << groupIt.first << "::" << result.first << ": " << result.second.mSeconds << " sec."; + } + } + + std::ostringstream msg; + msg << "Performance result for '" + groupIt.first + "'"; + LLOG(LogTitle) << msg.str(); + summary.push_back(msg.str()); + } + + LLOG(LogTitle) << "----"; + for (auto& str : summary) { + LLOG(LogTitle) << str; + } + } + } } diff --git a/packages/testing/source/common/Timer.cpp b/packages/testing/source/common/Timer.cpp index 7524e1b2..7c84b77f 100644 --- a/packages/testing/source/common/Timer.cpp +++ b/packages/testing/source/common/Timer.cpp @@ -1,6 +1,7 @@ #include #include +#include "logging/Log.h" #include "testing/Timer.h" namespace l { @@ -57,11 +58,27 @@ namespace testing { PerformanceTimer::PerformanceTimer(std::string_view group, std::string_view id) : mGroup(group), mId(id), mTimer([&](uint64_t nanoseconds) { auto& measure = get_time_measure(mGroup, mId); - - measure.mSeconds += static_cast(nanoseconds) / 1000000000.0; + auto measurement = static_cast(nanoseconds) / 1000000000.0; + measure.mSeconds += measurement; + measure.mMax = measure.mMax < measurement ? measurement : measure.mMax; + measure.mMin = measure.mMin > measurement ? measurement : measure.mMin; measure.mCount++; }) {} + + void show_measurements(const std::string& group) { + std::vector summary; + + LLOG(LogTitle) << "## " << group; + for (auto& it : get_time_measures(group)) { + auto& measure = it.second; + LLOG(LogInfo) << it.first; + auto mean = measure.mSeconds / static_cast(measure.mCount); + LLOG(LogInfo) << " mean " << mean << " sec"; + LLOG(LogInfo) << " min " << measure.mMin << " sec"; + LLOG(LogInfo) << " max " << measure.mMax << " sec"; + } + } } } diff --git a/packages/testing/tests/common/LoggingTest.cpp b/packages/testing/tests/common/LoggingTest.cpp index f60bba47..61105ac6 100644 --- a/packages/testing/tests/common/LoggingTest.cpp +++ b/packages/testing/tests/common/LoggingTest.cpp @@ -54,13 +54,13 @@ TEST(Logging, StringToHex) { TEST(Logging, StringCharacterConversion) { //auto s0 = L"ž © Õ ñ Ď Ĺ Ť Ɓ Ǯ Ƕ ɘ ʓ"; auto s1 = std::wstring(L"© Õ ñ"); - LOG(LogInfo) << s1; + LLOG(LogInfo) << s1; auto s2 = string::narrow(s1); - LOG(LogInfo) << s2; + LLOG(LogInfo) << s2; auto s3 = string::widen(s2); - LOG(LogInfo) << s3; + LLOG(LogInfo) << s3; auto s4 = string::narrow(s3); - LOG(LogInfo) << s4; + LLOG(LogInfo) << s4; TEST_TRUE(s3 == s1, "s1 did not equal s3"); TEST_TRUE(s4 == s2, "s2 did not equal s4"); @@ -169,13 +169,13 @@ PERF_TEST(LoggingTest, LogTimings) { { PERF_TIMER("LogTimings::LogInfo"); for (int i = 0; i < 10; i++) { - LOG(LogInfo) << "Test logging"; + LLOG(LogInfo) << "Test logging"; } } { PERF_TIMER("LogTimings::LogDebug"); for (int i = 0; i < 10; i++) { - LOG(LogDebug) << "Test logging"; + LLOG(LogDebug) << "Test logging"; } } diff --git a/packages/tools/source/common/utils/experimental/String.cpp b/packages/tools/source/common/utils/experimental/String.cpp index d2a28a19..8b6dcce2 100644 --- a/packages/tools/source/common/utils/experimental/String.cpp +++ b/packages/tools/source/common/utils/experimental/String.cpp @@ -34,7 +34,7 @@ void string::Append(SString& dst, const char src) *(dst.str + dst.cur_len++) = src; EndString(dst); - ASSERT(dst.cur_len < dst.max_len) << "SString has a illegal size (size must be less " << std::to_string(dst.max_len) << ", but is " << std::to_string(dst.cur_len); + //ASSERT(dst.cur_len < dst.max_len) << "SString has a illegal size (size must be less " << std::to_string(dst.max_len) << ", but is " << std::to_string(dst.cur_len); } void string::Append(SString& dst, const char* src) diff --git a/packages/tools/source/linux/utils/Process.cpp b/packages/tools/source/linux/utils/Process.cpp index 8331695a..216216dd 100644 --- a/packages/tools/source/linux/utils/Process.cpp +++ b/packages/tools/source/linux/utils/Process.cpp @@ -34,7 +34,7 @@ namespace process { return processRunner.get(); } else { - LOG(LogError) << "Failed to detach process"; + LLOG(LogError) << "Failed to detach process"; return FAILED_TO_DETACH_PROCESS; } } diff --git a/packages/tools/source/windows/platform/PlatformWindows.cpp b/packages/tools/source/windows/platform/PlatformWindows.cpp index 564de724..20ec7360 100644 --- a/packages/tools/source/windows/platform/PlatformWindows.cpp +++ b/packages/tools/source/windows/platform/PlatformWindows.cpp @@ -79,16 +79,16 @@ bool Proc::Execute(const std::wstring& file, const std::wstring& args, bool elev if (elevated) { ShExecInfo.lpVerb = L"runas"; - LOG(LogInfo) << "Attempt running application with elevated privileges.."; + LLOG(LogInfo) << "Attempt running application with elevated privileges.."; } if (!ShellExecuteExW(&ShExecInfo)) { auto err = GetLastError(); if (err) { - LOG(LogError) << "Error executing " << file << ". Error: " << err; + LLOG(LogError) << "Error executing " << file << ". Error: " << err; } else { - LOG(LogError) << "Unknown error occured"; + LLOG(LogError) << "Unknown error occured"; } return 1; } @@ -126,16 +126,16 @@ bool Proc::Fork(std::wstring title, std::wstring& file, const std::wstring& args &mProcessInfo) // Pointer to PROCESS_INFORMATION structure ) { - LOG(LogError) << "Failed to create process " << filename << ", " << std::to_string(static_cast(GetLastError())); + LLOG(LogError) << "Failed to create process " << filename << ", " << std::to_string(static_cast(GetLastError())); return false; } - LOG(LogInfo) << "Successfully started process '" << filename << " " << args << "'"; + LLOG(LogInfo) << "Successfully started process '" << filename << " " << args << "'"; if (!detach) { - LOG(LogInfo) << "Waiting for process exit"; + LLOG(LogInfo) << "Waiting for process exit"; DWORD waitResult = WaitForSingleObject(mProcessInfo.hProcess, INFINITE); if (waitResult == WAIT_FAILED) { - LOG(LogError) << "Wait for process failed " + std::to_string(static_cast(GetLastError())); + LLOG(LogError) << "Wait for process failed " + std::to_string(static_cast(GetLastError())); return false; } diff --git a/packages/tools/source/windows/utils/Opengl.cpp b/packages/tools/source/windows/utils/Opengl.cpp index bf59b696..b47005d3 100644 --- a/packages/tools/source/windows/utils/Opengl.cpp +++ b/packages/tools/source/windows/utils/Opengl.cpp @@ -21,7 +21,7 @@ namespace win32 { bool hasError = false; GLenum error; while((error = glGetError()) != GL_NO_ERROR) { - LOG(LogError) << "GL error: " << error; + LLOG(LogError) << "GL error: " << error; hasError = true; } return hasError; @@ -55,29 +55,29 @@ namespace win32 { HDC hDC = GetDC(windowData.mHWnd); if (!hDC) { - LOG(LogError) << "Failed to get window draw context (dc)"; + LLOG(LogError) << "Failed to get window draw context (dc)"; return std::nullopt; } int format = ChoosePixelFormat(hDC, &pfd); if (format == 0) { - LOG(LogError) << "Failed to choose pixel format"; + LLOG(LogError) << "Failed to choose pixel format"; return std::nullopt; } if (!SetPixelFormat(hDC, format, &pfd)) { - LOG(LogError) << "Failed to set pixel format"; + LLOG(LogError) << "Failed to set pixel format"; return std::nullopt; } HGLRC hRC = wglCreateContext(hDC); if (!wglMakeCurrent(hDC, hRC)) { - LOG(LogError) << "Failed to make current context"; + LLOG(LogError) << "Failed to make current context"; return std::nullopt; } - LOG(LogInfo) << "Available opengl version: " << glGetString(GL_VERSION); + LLOG(LogInfo) << "Available opengl version: " << glGetString(GL_VERSION); glEnable(GL_BLEND); @@ -96,7 +96,7 @@ namespace win32 { void glSwapBuffers(const OpenGLData& openGLData) { if (!wglSwapLayerBuffers(openGLData.mHandleDC, WGL_SWAP_MAIN_PLANE)) { - LOG(LogError) << "Failed to swap buffers"; + LLOG(LogError) << "Failed to swap buffers"; } } diff --git a/packages/tools/source/windows/utils/Process.cpp b/packages/tools/source/windows/utils/Process.cpp index 006b93ea..d67d6ac6 100644 --- a/packages/tools/source/windows/utils/Process.cpp +++ b/packages/tools/source/windows/utils/Process.cpp @@ -44,20 +44,20 @@ namespace process { &mProcessInfo) // Pointer to PROCESS_INFORMATION structure ) { - LOG(LogError) << "Failed to create process " << filename << ", " << std::to_string(static_cast(GetLastError())); + LLOG(LogError) << "Failed to create process " << filename << ", " << std::to_string(static_cast(GetLastError())); return FAILED_TO_CREATE_PROCESS; } if (!mDetach) { - LOG(LogInfo) << "Successfully started process " << filename << " " << mArgs; + LLOG(LogInfo) << "Successfully started process " << filename << " " << mArgs; // Wait until child process exits. DWORD waitResult = WaitForSingleObject(mProcessInfo.hProcess, INFINITE); if (waitResult == WAIT_FAILED) { - LOG(LogError) << "Wait for process failed " + std::to_string(static_cast(GetLastError())); + LLOG(LogError) << "Wait for process failed " + std::to_string(static_cast(GetLastError())); return FAILED_TO_WAIT_FOR_PROCESS; } else { - LOG(LogInfo) << "Wait ended for process " << filename; + LLOG(LogInfo) << "Wait ended for process " << filename; } } @@ -84,7 +84,7 @@ namespace process { return processRunner.get(); } else { - LOG(LogError) << "Failed to detach process"; + LLOG(LogError) << "Failed to detach process"; return FAILED_TO_DETACH_PROCESS; } } diff --git a/packages/tools/source/windows/utils/Socket.cpp b/packages/tools/source/windows/utils/Socket.cpp index d9125e73..a62351d8 100644 --- a/packages/tools/source/windows/utils/Socket.cpp +++ b/packages/tools/source/windows/utils/Socket.cpp @@ -12,11 +12,11 @@ namespace l { int xsend(SOCKET s, const char *buf, size_t len, int flags) { int result = send(s, (const char *)buf, (int)len, flags); if (result == SOCKET_ERROR) { - LOG(LogWarning) << "Failed to write to socket " << s << ", error: " << WSAGetLastError(); + LLOG(LogWarning) << "Failed to write to socket " << s << ", error: " << WSAGetLastError(); return result; } if (result == 0) { - LOG(LogWarning) << "Socket " << s << " closed gracefully"; + LLOG(LogWarning) << "Socket " << s << " closed gracefully"; return result; } return result; @@ -25,11 +25,11 @@ namespace l { int xrecv(SOCKET s, char *buf, size_t len, int flags) { int result = recv(s, (char *)buf, (int)len, flags); if (result == SOCKET_ERROR) { - LOG(LogWarning) << "Failed to read from socket " << s; + LLOG(LogWarning) << "Failed to read from socket " << s; return result; } if (result == 0) { - LOG(LogWarning) << "Socket " << s << " closed gracefully"; + LLOG(LogWarning) << "Socket " << s << " closed gracefully"; return result; } return result; @@ -38,7 +38,7 @@ namespace l { bool xclose(SOCKET clientSocket) { int result = shutdown(clientSocket, SD_SEND); if (result == SOCKET_ERROR) { - LOG(LogError) << "Shutdown failed with error: " << WSAGetLastError(); + LLOG(LogError) << "Shutdown failed with error: " << WSAGetLastError(); closesocket(clientSocket); return false; } @@ -58,7 +58,7 @@ namespace l { auto iResult = WSAStartup(version, &WSAData); if (iResult != 0) { - LOG(LogError) << "WSAStartup failed with error: " << iResult; + LLOG(LogError) << "WSAStartup failed with error: " << iResult; } if (LOBYTE(WSAData.wVersion) == LOBYTE(version) && HIBYTE(WSAData.wVersion) == HIBYTE(version)) { initialized = true; @@ -83,10 +83,10 @@ namespace l { SOCKET s = accept(mListenSocket, (SOCKADDR*)&addr, &addrlen); const char *ip = inet_ntoa(addr.sin_addr); if (s == INVALID_SOCKET) { - LOG(LogError) << "Failed connection from " << ip << ":" << addr.sin_port << ", error: " << WSAGetLastError(); + LLOG(LogError) << "Failed connection from " << ip << ":" << addr.sin_port << ", error: " << WSAGetLastError(); return nullptr; } - LOG(LogInfo) << "Accept connection from " << ip << ":" << addr.sin_port; + LLOG(LogInfo) << "Accept connection from " << ip << ":" << addr.sin_port; return std::make_unique(s, ip, addr.sin_port); } @@ -124,14 +124,14 @@ namespace l { // Resolve the server address and port int iResult = getaddrinfo(NULL, std::to_string(port).c_str(), &hints, &result); if (iResult != 0) { - LOG(LogError) << "getaddrinfo failed with error: " << iResult; + LLOG(LogError) << "getaddrinfo failed with error: " << iResult; return nullptr; } // Create a SOCKET for connecting to server SOCKET listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if (listenSocket == INVALID_SOCKET) { - LOG(LogError) << "socket failed with error: " << WSAGetLastError(); + LLOG(LogError) << "socket failed with error: " << WSAGetLastError(); freeaddrinfo(result); return nullptr; } @@ -139,7 +139,7 @@ namespace l { // Setup the TCP listening socket iResult = bind(listenSocket, result->ai_addr, (int)result->ai_addrlen); if (iResult == SOCKET_ERROR) { - LOG(LogError) << "bind failed with error: " << WSAGetLastError(); + LLOG(LogError) << "bind failed with error: " << WSAGetLastError(); freeaddrinfo(result); closesocket(listenSocket); return nullptr; @@ -149,7 +149,7 @@ namespace l { iResult = listen(listenSocket, SOMAXCONN); if (iResult == SOCKET_ERROR) { - LOG(LogError) << "listen failed with error: " << WSAGetLastError(); + LLOG(LogError) << "listen failed with error: " << WSAGetLastError(); closesocket(listenSocket); return nullptr; } @@ -173,7 +173,7 @@ namespace l { // Resolve the server address and port iResult = getaddrinfo(host, std::to_string(port).c_str(), &hints, &result); if (iResult != 0) { - LOG(LogError) << "getaddrinfo failed with error: " << iResult; + LLOG(LogError) << "getaddrinfo failed with error: " << iResult; return nullptr; } @@ -183,7 +183,7 @@ namespace l { // Create a SOCKET for connecting to server connectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (connectSocket == INVALID_SOCKET) { - LOG(LogError) << "socket failed with error: " << WSAGetLastError(); + LLOG(LogError) << "socket failed with error: " << WSAGetLastError(); return nullptr; } @@ -200,7 +200,7 @@ namespace l { freeaddrinfo(result); if (connectSocket == INVALID_SOCKET) { - LOG(LogError) << "Unable to connect to server!"; + LLOG(LogError) << "Unable to connect to server!"; return nullptr; } diff --git a/packages/tools/source/windows/utils/Win32.cpp b/packages/tools/source/windows/utils/Win32.cpp index d2616ace..52f6492c 100644 --- a/packages/tools/source/windows/utils/Win32.cpp +++ b/packages/tools/source/windows/utils/Win32.cpp @@ -47,7 +47,7 @@ namespace { } } else { - LOG(LogError) << "Failed to locate window callback: " << l::string::narrow(className); + LLOG(LogError) << "Failed to locate window callback: " << l::string::narrow(className); } } } @@ -83,30 +83,30 @@ namespace l { switch (msg) { case WM_ACTIVATEAPP: - LOG(LogDebug) << "[" << hwnd << "] WM_ACTIVATEAPP recieved [" << wp << ", " << lp << "]"; + LLOG(LogDebug) << "[" << hwnd << "] WM_ACTIVATEAPP recieved [" << wp << ", " << lp << "]"; break; case WM_ACTIVATE: - LOG(LogDebug) << "[" << hwnd << "] WM_ACTIVATE recieved [" << wp << ", " << lp << "]"; + LLOG(LogDebug) << "[" << hwnd << "] WM_ACTIVATE recieved [" << wp << ", " << lp << "]"; break; case WM_NCCREATE: - LOG(LogDebug) << "[" << hwnd << "] WM_NCCREATE recieved [" << wp << ", " << lp << "]"; + LLOG(LogDebug) << "[" << hwnd << "] WM_NCCREATE recieved [" << wp << ", " << lp << "]"; break; case WM_GETMINMAXINFO: - //LOG(LogDebug) << "[" << hwnd << "] WM_GETMINMAXINFO recieved [" << wp << ", " << lp << "]"; + //LLOG(LogDebug) << "[" << hwnd << "] WM_GETMINMAXINFO recieved [" << wp << ", " << lp << "]"; break; case WM_CREATE: - LOG(LogDebug) << "[" << hwnd << "] WM_CREATE recieved [" << wp << ", " << lp << "]"; + LLOG(LogDebug) << "[" << hwnd << "] WM_CREATE recieved [" << wp << ", " << lp << "]"; break; case WM_GETICON: // Called regulary to update large and small app icon break; case WM_CLOSE: - LOG(LogDebug) << "[" << hwnd << "] WM_CLOSE recieved [" << wp << ", " << lp << "]"; + LLOG(LogDebug) << "[" << hwnd << "] WM_CLOSE recieved [" << wp << ", " << lp << "]"; break; case WM_DESTROY: - LOG(LogDebug) << "[" << hwnd << "] WM_DESTROY recieved [" << wp << ", " << lp << "]"; + LLOG(LogDebug) << "[" << hwnd << "] WM_DESTROY recieved [" << wp << ", " << lp << "]"; break; case WM_NCDESTROY: - LOG(LogDebug) << "[" << hwnd << "] WM_NCDESTROY recieved [" << wp << ", " << lp << "]"; + LLOG(LogDebug) << "[" << hwnd << "] WM_NCDESTROY recieved [" << wp << ", " << lp << "]"; EraseWindowCallback(hwnd); } return DefWindowProcW(hwnd, msg, wp, lp); @@ -141,13 +141,13 @@ namespace l { return std::make_optional(name, std::move(wndClassEx), hWnd); } else { - LOG(LogError) << "Failed to create window, error " << GetLastError(); + LLOG(LogError) << "Failed to create window, error " << GetLastError(); } } else { - LOG(LogError) << "Failed to register window class. Error: " << GetLastError(); + LLOG(LogError) << "Failed to register window class. Error: " << GetLastError(); } - LOG(LogError) << "See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes for more info"; + LLOG(LogError) << "See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes for more info"; return std::nullopt; }