| /* | |
| * Copyright 2025 Rive | |
| */ | |
| #pragma once | |
| #include "rive/command_queue.hpp" | |
| #include "rive/hit_result.hpp" | |
| #include "rive/math/mat2d.hpp" | |
| #include <iostream> | |
| #include <sstream> | |
| #include <thread> | |
| #include <unordered_map> | |
| #include <type_traits> | |
| namespace rive | |
| { | |
| class ScriptingContext; | |
| // Server-side worker that executes commands from a CommandQueue. | |
| class CommandServer | |
| { | |
| public: | |
| CommandServer(rcp<CommandQueue>, | |
| Factory*, | |
| rcp<rive::FileAssetLoader> = nullptr); | |
| virtual ~CommandServer(); | |
| Factory* factory() const { return m_factory; } | |
| rive::HitResult pointerDownSynchronized(StateMachineHandle, | |
| const CommandQueue::PointerEvent&); | |
| rive::HitResult pointerMoveSynchronized(StateMachineHandle, | |
| const CommandQueue::PointerEvent&); | |
| rive::HitResult pointerUpSynchronized(StateMachineHandle, | |
| const CommandQueue::PointerEvent&); | |
| File* getFile(FileHandle) const; | |
| bool getWasDisconnected() const { return m_wasDisconnectReceived; } | |
| RenderImage* getImage(RenderImageHandle) const; | |
| AudioSource* getAudioSource(AudioSourceHandle) const; | |
| Font* getFont(FontHandle) const; | |
| BlobAsset* getBlob(BlobAssetHandle) const; | |
| ArtboardInstance* getArtboardInstance(ArtboardHandle) const; | |
| rcp<BindableArtboard> getBindableArtboard(ArtboardHandle) const; | |
| StateMachineInstance* getStateMachineInstance(StateMachineHandle) const; | |
| ViewModelInstanceRuntime* getViewModelInstance( | |
| ViewModelInstanceHandle) const; | |
| ViewModelInstanceHandle getHandleForInstance( | |
| ViewModelInstanceRuntime*) const; | |
| // Wait for queue to not be empty, then returns pollMessages. | |
| bool waitCommands(); | |
| // Returns imidiatly after checking messages. If there are none just returns | |
| // returns !m_wasDisconnectReceived. | |
| bool processCommands(); | |
| // Blocks and runs waitMessages until disconnect is received. | |
| void serveUntilDisconnect(); | |
| struct Subscription | |
| { | |
| // The request Id for sbuscribing to this particular property. | |
| uint64_t requestId; | |
| // Information about the property we want to "subsribe" to. | |
| PropertyData data; | |
| // The root view model of from the perspective of the path in data.name. | |
| ViewModelInstanceHandle rootViewModel; | |
| }; | |
| #ifdef TESTING | |
| // Expose cursorPosForPointerEvent for testing. | |
| Vec2D testing_cursorPosForPointerEvent(StateMachineInstance* instance, | |
| CommandQueue::PointerEvent event) | |
| { | |
| return cursorPosForPointerEvent(instance, event); | |
| } | |
| const std::vector<Subscription>& testing_getSubsciptions() const | |
| { | |
| return m_propertySubscriptions; | |
| } | |
| RenderImageHandle testing_globalImageNamed(std::string name); | |
| AudioSourceHandle testing_globalAudioNamed(std::string name); | |
| FontHandle testing_globalFontNamed(std::string name); | |
| bool testing_globalImageContains(std::string name); | |
| bool testing_globalAudioContains(std::string name); | |
| bool testing_globalFontContains(std::string name); | |
| #ifndef NDEBUG | |
| // Used because "sync pointer events" test needs to be able to | |
| // make the server on one thread but access it from another | |
| void testing_overrideThreadID(std::thread::id threadID) | |
| { | |
| m_threadID = threadID; | |
| } | |
| #endif | |
| #endif | |
| bool focusNextSynchronized(StateMachineHandle); | |
| bool focusPreviousSynchronized(StateMachineHandle); | |
| private: | |
| friend class CommandQueue; | |
| template <typename HandleType> class ErrorReporter | |
| { | |
| public: | |
| ErrorReporter(CommandServer* server, | |
| HandleType handle, | |
| uint64_t requestId, | |
| CommandQueue::Message message) : | |
| m_server(server), m_lock(m_server->m_commandQueue->m_messageMutex) | |
| { | |
| assert(server); | |
| assert(message >= CommandQueue::Message::fileError && | |
| message <= CommandQueue::Message::stateMachineError); | |
| std::cerr << "ERROR : "; | |
| m_server->m_commandQueue->m_messageStream << message; | |
| m_server->m_commandQueue->m_messageStream << handle; | |
| m_server->m_commandQueue->m_messageStream << requestId; | |
| } | |
| ErrorReporter& operator<<(std::vector<std::string> vector) | |
| { | |
| m_ostringstream << "{ "; | |
| for (auto& s : vector) | |
| m_ostringstream << s << ","; | |
| m_ostringstream << "} "; | |
| return *this; | |
| } | |
| template <typename T> ErrorReporter& operator<<(const T& t) | |
| { | |
| m_ostringstream << t; | |
| return *this; | |
| } | |
| ~ErrorReporter() | |
| { | |
| std::string str = m_ostringstream.str(); | |
| assert(!str.empty()); | |
| std::cerr << str << "\n"; | |
| m_server->m_commandQueue->m_messageNames << std::move(str); | |
| } | |
| CommandServer* m_server; | |
| std::unique_lock<std::mutex> m_lock; | |
| std::ostringstream m_ostringstream; | |
| }; | |
| void checkPropertySubscriptions(); | |
| Vec2D cursorPosForPointerEvent(StateMachineInstance*, | |
| const CommandQueue::PointerEvent&); | |
| void cleanupArtboard(ArtboardHandle handle, uint64_t requestId) | |
| { | |
| auto itr = m_artboards.find(handle); | |
| if (itr != m_artboards.end()) | |
| { | |
| auto dependencyItr = m_artboardDependencies.find(handle); | |
| if (dependencyItr != m_artboardDependencies.end()) | |
| { | |
| auto& stateMachineVector = dependencyItr->second; | |
| for (auto stateMachine : stateMachineVector) | |
| { | |
| if (m_stateMachines.erase(stateMachine) > 0) | |
| { | |
| std::unique_lock<std::mutex> lock( | |
| m_commandQueue->m_messageMutex); | |
| m_commandQueue->m_messageStream | |
| << CommandQueue::Message::stateMachineDeleted; | |
| m_commandQueue->m_messageStream << stateMachine; | |
| m_commandQueue->m_messageStream << requestId; | |
| } | |
| } | |
| m_artboardDependencies.erase(dependencyItr); | |
| } | |
| m_artboards.erase(itr); | |
| std::unique_lock<std::mutex> lock(m_commandQueue->m_messageMutex); | |
| m_commandQueue->m_messageStream | |
| << CommandQueue::Message::artboardDeleted; | |
| m_commandQueue->m_messageStream << handle; | |
| m_commandQueue->m_messageStream << requestId; | |
| } | |
| } | |
| struct SynchronizedStateMachine : RefCnt<SynchronizedStateMachine> | |
| { | |
| // Kept out of line to avoid including StateMachineInstance in the main | |
| // CommandServer header. | |
| ~SynchronizedStateMachine(); | |
| SynchronizedStateMachine& operator=(SynchronizedStateMachine&& other); | |
| SynchronizedStateMachine() = default; | |
| SynchronizedStateMachine( | |
| std::unique_ptr<StateMachineInstance> instance); | |
| std::unique_ptr<StateMachineInstance> instance; | |
| Mat2D m_lastSemanticsTransform; | |
| bool m_hasLastSemanticsTransform = false; | |
| // This mutex ensures that a specific state machine instance is not | |
| // advanced while a synchronized input or focus operation is running. | |
| std::mutex m_mutex; | |
| }; | |
| rcp<CommandServer::SynchronizedStateMachine> getStateMachineWrapper( | |
| StateMachineHandle handle); | |
| bool m_wasDisconnectReceived = false; | |
| const rcp<CommandQueue> m_commandQueue; | |
| Factory* const m_factory; | |
| #ifndef NDEBUG | |
| #ifdef TESTING | |
| std::thread::id m_threadID; | |
| #else | |
| const std::thread::id m_threadID; | |
| #endif | |
| #endif | |
| // Vector to iterate on for subscriptions. This is a vector instead of a map | |
| // because we iterate through the entire vector every frame anyway. | |
| std::vector<Subscription> m_propertySubscriptions; | |
| // Dependencies | |
| // When a file gets deleted artboards and statemachine become invalid. Here | |
| // we hold a reference to the artboard only because that artboard has a | |
| // dependency to the State Machine. | |
| std::unordered_map<FileHandle, std::vector<ArtboardHandle>> | |
| m_fileDependencies; | |
| // When an artboard gets deleted the statemachine assosiated with it is also | |
| // now invalid. | |
| std::unordered_map<ArtboardHandle, std::vector<StateMachineHandle>> | |
| m_artboardDependencies; | |
| // Handle Maps | |
| std::unordered_map<FileHandle, rcp<File>> m_files; | |
| std::unordered_map<FontHandle, rcp<Font>> m_fonts; | |
| std::unordered_map<RenderImageHandle, rcp<RenderImage>> m_images; | |
| std::unordered_map<BlobAssetHandle, rcp<BlobAsset>> m_blobs; | |
| std::unordered_map<AudioSourceHandle, rcp<AudioSource>> m_audioSources; | |
| std::unordered_map<ArtboardHandle, rcp<BindableArtboard>> m_artboards; | |
| std::unordered_map<ViewModelInstanceHandle, rcp<ViewModelInstanceRuntime>> | |
| m_viewModels; | |
| std::unordered_map<StateMachineHandle, rcp<SynchronizedStateMachine>> | |
| m_stateMachines; | |
| std::mutex m_stateMachineAccessMutex; | |
| std::unordered_map<DrawKey, CommandServerDrawCallback> m_uniqueDraws; | |
| class CommandFileAssetLoader; | |
| rcp<CommandFileAssetLoader> m_fileAssetLoader; | |
| }; | |
| }; // namespace rive |