mirror of
https://github.com/rive-app/rive-cpp.git
synced 2026-01-18 13:11:19 +01:00
* make file ref counted * migrate File to refcnt * add bindable artboard class to keep file reference * feat(unity): support rcp file and BindableArtboard class (#10228) * refactor(apple): use updated file bindable artboard apis (#10229) * update command queue to support bindable artboards * use bindable artboards on command queue * self manage view model instances in js runtime * change remaining viewModelInstanceRuntimes to rcp * refactor(apple): update view model instances to use rcp (#10298) * refactor(unity): support rcp ViewModelInstanceRuntime (#10309) * rebase fix * deprecate getArtboard in favor of getBindableArtboard * fix merge * remove unused lambda capture * fix rive binding * feat(Android): RCP File, VMI, and add bindable artboards (#10456) * refactor: C++ refactors - Import from long (incorrect) -> jlong - Header clang-tidy fix - Use reinterpret_cast instead of C-style cast - Break out some variables instead of long one liners - Use auto - Remove unused env and thisObj # Conflicts: # packages/runtime_android/kotlin/src/main/cpp/src/helpers/general.cpp * docs: Improve documentation on the File class * feat: Support bindable artboard type and RCP VMIs # Conflicts: # packages/runtime_android/kotlin/src/androidTest/java/app/rive/runtime/kotlin/core/RiveDataBindingTest.kt * feat: Support RCP files * refactor: Change from +1/-1 ref to just release * fix: Moved to the more appropriate null pointer for GetStringUTFChars * replace unref with release Co-authored-by: Adam <67035612+damzobridge@users.noreply.github.com> Co-authored-by: David Skuza <david@rive.app> Co-authored-by: Erik <erik@rive.app> Co-authored-by: hernan <hernan@rive.app>
4465 lines
167 KiB
C++
4465 lines
167 KiB
C++
/*
|
|
* Copyright 2025 Rive
|
|
*/
|
|
|
|
#include "rive/animation/state_machine_input_instance.hpp"
|
|
#include "rive/animation/state_machine_instance.hpp"
|
|
#include "rive/command_queue.hpp"
|
|
#include "rive/command_server.hpp"
|
|
#include "rive/file.hpp"
|
|
#include "common/render_context_null.hpp"
|
|
#include <fstream>
|
|
|
|
namespace rive
|
|
{
|
|
bool operator==(const ViewModelEnum& left, const ViewModelEnum& right)
|
|
{
|
|
if (left.name != right.name ||
|
|
left.enumerants.size() != right.enumerants.size())
|
|
return false;
|
|
|
|
for (int i = 0; i < left.enumerants.size(); ++i)
|
|
{
|
|
if (left.enumerants[i] != right.enumerants[i])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool operator!=(const ViewModelEnum& left, const ViewModelEnum& right)
|
|
{
|
|
return !(left == right);
|
|
}
|
|
bool operator==(const PropertyData& l, const PropertyData& r)
|
|
{
|
|
return l.name == r.name && l.type == r.type;
|
|
}
|
|
bool operator==(const CommandQueue::FileListener::ViewModelPropertyData& l,
|
|
const CommandQueue::FileListener::ViewModelPropertyData& r)
|
|
{
|
|
return l.name == r.name && l.type == r.type && l.metaData == r.metaData;
|
|
}
|
|
bool operator!=(const CommandQueue::FileListener::ViewModelPropertyData& l,
|
|
const CommandQueue::FileListener::ViewModelPropertyData& r)
|
|
{
|
|
return !(l == r);
|
|
}
|
|
} // namespace rive
|
|
|
|
template <typename t, size_t arraySize>
|
|
bool operator==(const std::vector<t>& left,
|
|
const std::array<t, arraySize>& right)
|
|
{
|
|
if (left.size() != arraySize)
|
|
return false;
|
|
|
|
for (int i = 0; i < left.size(); ++i)
|
|
{
|
|
if (left[i] != right[i])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename t>
|
|
bool operator==(const std::vector<t>& left, const std::vector<t>& right)
|
|
{
|
|
if (left.size() != right.size())
|
|
return false;
|
|
|
|
for (int i = 0; i < left.size(); ++i)
|
|
{
|
|
if (left[i] != right[i])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#include "catch.hpp"
|
|
|
|
using namespace rive;
|
|
|
|
static void server_thread(rcp<CommandQueue> commandQueue)
|
|
{
|
|
std::unique_ptr<gpu::RenderContext> nullContext =
|
|
RenderContextNULL::MakeContext();
|
|
CommandServer server(std::move(commandQueue), nullContext.get());
|
|
server.serveUntilDisconnect();
|
|
}
|
|
|
|
static void wait_for_server(CommandQueue* commandQueue)
|
|
{
|
|
std::mutex mutex;
|
|
std::condition_variable cv;
|
|
bool complete = false;
|
|
std::unique_lock<std::mutex> lock(mutex);
|
|
commandQueue->runOnce([&](CommandServer*) {
|
|
std::unique_lock<std::mutex> serverLock(mutex);
|
|
complete = true;
|
|
cv.notify_one();
|
|
});
|
|
while (!complete)
|
|
cv.wait(lock);
|
|
}
|
|
|
|
class TestPODStream : public RefCnt<TestPODStream>
|
|
{
|
|
public:
|
|
static constexpr int MAG_NUMBER = 0x99;
|
|
int m_number = MAG_NUMBER;
|
|
};
|
|
|
|
TEST_CASE("POD Stream RCP", "[PODStream]")
|
|
{
|
|
PODStream stream;
|
|
rcp<TestPODStream> t1 = make_rcp<TestPODStream>();
|
|
TestPODStream* orig = t1.get();
|
|
stream << t1;
|
|
|
|
CHECK(t1.get() != nullptr);
|
|
CHECK(t1.get() == orig);
|
|
|
|
rcp<TestPODStream> t2;
|
|
stream >> t2;
|
|
CHECK(t2.get() == orig);
|
|
CHECK(t2->m_number == TestPODStream::MAG_NUMBER);
|
|
|
|
rcp<TestPODStream> t3;
|
|
stream << t3;
|
|
rcp<TestPODStream> t4;
|
|
stream >> t4;
|
|
CHECK(t4.get() == nullptr);
|
|
}
|
|
|
|
TEST_CASE("artboard management", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/two_artboards.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
commandQueue->runOnce([fileHandle](CommandServer* server) {
|
|
CHECK(fileHandle != RIVE_NULL_HANDLE);
|
|
CHECK(server->getFile(fileHandle) != nullptr);
|
|
});
|
|
|
|
ArtboardHandle artboardHandle1 =
|
|
commandQueue->instantiateArtboardNamed(fileHandle, "One");
|
|
ArtboardHandle artboardHandle2 =
|
|
commandQueue->instantiateArtboardNamed(fileHandle, "Two");
|
|
ArtboardHandle artboardHandle3 =
|
|
commandQueue->instantiateArtboardNamed(fileHandle, "Three");
|
|
commandQueue->runOnce([artboardHandle1, artboardHandle2, artboardHandle3](
|
|
CommandServer* server) {
|
|
CHECK(artboardHandle1 != RIVE_NULL_HANDLE);
|
|
CHECK(server->getArtboardInstance(artboardHandle1) != nullptr);
|
|
CHECK(artboardHandle2 != RIVE_NULL_HANDLE);
|
|
CHECK(server->getArtboardInstance(artboardHandle2) != nullptr);
|
|
// An artboard named "Three" doesn't exist.
|
|
CHECK(artboardHandle3 != RIVE_NULL_HANDLE);
|
|
CHECK(server->getArtboardInstance(artboardHandle3) == nullptr);
|
|
});
|
|
|
|
// Deleting an invalid handle has no effect.
|
|
commandQueue->deleteArtboard(artboardHandle3);
|
|
commandQueue->deleteArtboard(artboardHandle2);
|
|
commandQueue->runOnce([artboardHandle1, artboardHandle2, artboardHandle3](
|
|
CommandServer* server) {
|
|
CHECK(server->getArtboardInstance(artboardHandle1) != nullptr);
|
|
CHECK(server->getArtboardInstance(artboardHandle2) == nullptr);
|
|
CHECK(server->getArtboardInstance(artboardHandle3) == nullptr);
|
|
});
|
|
|
|
// Deleting the file first should now delete the artboard as well.
|
|
commandQueue->deleteFile(fileHandle);
|
|
commandQueue->runOnce([fileHandle](CommandServer* server) {
|
|
CHECK(server->getFile(fileHandle) == nullptr);
|
|
});
|
|
|
|
commandQueue->deleteArtboard(artboardHandle1);
|
|
commandQueue->runOnce([artboardHandle1](CommandServer* server) {
|
|
CHECK(server->getArtboardInstance(artboardHandle1) == nullptr);
|
|
});
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
TEST_CASE("state machine management", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/multiple_state_machines.riv",
|
|
std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
ArtboardHandle artboardHandle =
|
|
commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
commandQueue->runOnce([fileHandle, artboardHandle](CommandServer* server) {
|
|
CHECK(fileHandle != RIVE_NULL_HANDLE);
|
|
CHECK(server->getFile(fileHandle) != nullptr);
|
|
CHECK(artboardHandle != RIVE_NULL_HANDLE);
|
|
CHECK(server->getArtboardInstance(artboardHandle) != nullptr);
|
|
});
|
|
|
|
StateMachineHandle sm1 =
|
|
commandQueue->instantiateStateMachineNamed(artboardHandle, "one");
|
|
StateMachineHandle sm2 =
|
|
commandQueue->instantiateStateMachineNamed(artboardHandle, "two");
|
|
StateMachineHandle sm3 =
|
|
commandQueue->instantiateStateMachineNamed(artboardHandle, "blahblah");
|
|
commandQueue->runOnce([sm1, sm2, sm3](CommandServer* server) {
|
|
CHECK(sm1 != RIVE_NULL_HANDLE);
|
|
CHECK(server->getStateMachineInstance(sm1) != nullptr);
|
|
CHECK(sm2 != RIVE_NULL_HANDLE);
|
|
CHECK(server->getStateMachineInstance(sm2) != nullptr);
|
|
// A state machine named "blahblah" doesn't exist.
|
|
CHECK(sm3 != RIVE_NULL_HANDLE);
|
|
CHECK(server->getStateMachineInstance(sm3) == nullptr);
|
|
});
|
|
|
|
commandQueue->deleteFile(fileHandle);
|
|
commandQueue->deleteArtboard(artboardHandle);
|
|
commandQueue->deleteStateMachine(sm1);
|
|
commandQueue->runOnce(
|
|
[fileHandle, artboardHandle, sm1, sm2](CommandServer* server) {
|
|
CHECK(server->getFile(fileHandle) == nullptr);
|
|
CHECK(server->getArtboardInstance(artboardHandle) == nullptr);
|
|
CHECK(server->getStateMachineInstance(sm1) == nullptr);
|
|
// Because of the dependencies, this should delete the statemachine
|
|
// as well.
|
|
CHECK(server->getStateMachineInstance(sm2) == nullptr);
|
|
});
|
|
|
|
commandQueue->deleteStateMachine(sm2);
|
|
commandQueue->runOnce([sm2](CommandServer* server) {
|
|
CHECK(server->getStateMachineInstance(sm2) == nullptr);
|
|
});
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
TEST_CASE("default artboard & state machine", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/entry.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
ArtboardHandle artboardHandle =
|
|
commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
StateMachineHandle smHandle =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle);
|
|
commandQueue->runOnce([artboardHandle, smHandle](CommandServer* server) {
|
|
rive::ArtboardInstance* artboard =
|
|
server->getArtboardInstance(artboardHandle);
|
|
REQUIRE(artboard != nullptr);
|
|
CHECK(artboard->name() == "New Artboard");
|
|
rive::StateMachineInstance* sm =
|
|
server->getStateMachineInstance(smHandle);
|
|
REQUIRE(sm != nullptr);
|
|
CHECK(sm->name() == "State Machine 1");
|
|
});
|
|
|
|
// Using an empty string is the same as requesting the default.
|
|
ArtboardHandle artboardHandle2 =
|
|
commandQueue->instantiateArtboardNamed(fileHandle, "");
|
|
StateMachineHandle smHandle2 =
|
|
commandQueue->instantiateStateMachineNamed(artboardHandle2, "");
|
|
commandQueue->runOnce([artboardHandle2, smHandle2](CommandServer* server) {
|
|
rive::ArtboardInstance* artboard =
|
|
server->getArtboardInstance(artboardHandle2);
|
|
REQUIRE(artboard != nullptr);
|
|
CHECK(artboard->name() == "New Artboard");
|
|
rive::StateMachineInstance* sm =
|
|
server->getStateMachineInstance(smHandle2);
|
|
REQUIRE(sm != nullptr);
|
|
CHECK(sm->name() == "State Machine 1");
|
|
});
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
TEST_CASE("invalid handles", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/entry.riv", std::ios::binary);
|
|
FileHandle goodFile = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
FileHandle badFile =
|
|
commandQueue->loadFile(std::vector<uint8_t>(100 * 1024, 0));
|
|
commandQueue->runOnce([goodFile, badFile](CommandServer* server) {
|
|
CHECK(goodFile != RIVE_NULL_HANDLE);
|
|
CHECK(server->getFile(goodFile) != nullptr);
|
|
CHECK(badFile != RIVE_NULL_HANDLE);
|
|
CHECK(server->getFile(badFile) == nullptr);
|
|
});
|
|
|
|
ArtboardHandle goodArtboard =
|
|
commandQueue->instantiateArtboardNamed(goodFile, "New Artboard");
|
|
ArtboardHandle badArtboard1 =
|
|
commandQueue->instantiateDefaultArtboard(badFile);
|
|
ArtboardHandle badArtboard2 =
|
|
commandQueue->instantiateArtboardNamed(badFile, "New Artboard");
|
|
ArtboardHandle badArtboard3 =
|
|
commandQueue->instantiateArtboardNamed(goodFile, "blahblahblah");
|
|
commandQueue->runOnce(
|
|
[goodArtboard, badArtboard1, badArtboard2, badArtboard3](
|
|
CommandServer* server) {
|
|
CHECK(goodArtboard != RIVE_NULL_HANDLE);
|
|
CHECK(server->getArtboardInstance(goodArtboard) != nullptr);
|
|
CHECK(badArtboard1 != RIVE_NULL_HANDLE);
|
|
CHECK(server->getArtboardInstance(badArtboard1) == nullptr);
|
|
CHECK(badArtboard2 != RIVE_NULL_HANDLE);
|
|
CHECK(server->getArtboardInstance(badArtboard2) == nullptr);
|
|
CHECK(badArtboard3 != RIVE_NULL_HANDLE);
|
|
CHECK(server->getArtboardInstance(badArtboard3) == nullptr);
|
|
});
|
|
|
|
StateMachineHandle goodSM =
|
|
commandQueue->instantiateStateMachineNamed(goodArtboard,
|
|
"State Machine 1");
|
|
StateMachineHandle badSM1 =
|
|
commandQueue->instantiateStateMachineNamed(badArtboard2,
|
|
"State Machine 1");
|
|
StateMachineHandle badSM2 =
|
|
commandQueue->instantiateStateMachineNamed(goodArtboard,
|
|
"blahblahblah");
|
|
StateMachineHandle badSM3 =
|
|
commandQueue->instantiateDefaultStateMachine(badArtboard3);
|
|
commandQueue->runOnce(
|
|
[goodSM, badSM1, badSM2, badSM3](CommandServer* server) {
|
|
CHECK(goodSM != RIVE_NULL_HANDLE);
|
|
CHECK(server->getStateMachineInstance(goodSM) != nullptr);
|
|
CHECK(badSM1 != RIVE_NULL_HANDLE);
|
|
CHECK(server->getStateMachineInstance(badSM1) == nullptr);
|
|
CHECK(badSM2 != RIVE_NULL_HANDLE);
|
|
CHECK(server->getStateMachineInstance(badSM2) == nullptr);
|
|
CHECK(badSM3 != RIVE_NULL_HANDLE);
|
|
CHECK(server->getStateMachineInstance(badSM3) == nullptr);
|
|
});
|
|
|
|
commandQueue->deleteStateMachine(badSM3);
|
|
commandQueue->deleteStateMachine(badSM2);
|
|
commandQueue->deleteStateMachine(badSM1);
|
|
commandQueue->deleteArtboard(badArtboard3);
|
|
commandQueue->deleteArtboard(badArtboard2);
|
|
commandQueue->deleteArtboard(badArtboard1);
|
|
commandQueue->deleteFile(badFile);
|
|
commandQueue->runOnce(
|
|
[goodFile, goodArtboard, goodSM](CommandServer* server) {
|
|
CHECK(server->getFile(goodFile) != nullptr);
|
|
CHECK(server->getArtboardInstance(goodArtboard) != nullptr);
|
|
CHECK(server->getStateMachineInstance(goodSM) != nullptr);
|
|
});
|
|
|
|
commandQueue->deleteStateMachine(goodSM);
|
|
commandQueue->deleteArtboard(goodArtboard);
|
|
commandQueue->deleteFile(goodFile);
|
|
commandQueue->runOnce(
|
|
[goodFile, goodArtboard, goodSM](CommandServer* server) {
|
|
CHECK(server->getFile(goodFile) == nullptr);
|
|
CHECK(server->getArtboardInstance(goodArtboard) == nullptr);
|
|
CHECK(server->getStateMachineInstance(goodSM) == nullptr);
|
|
});
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
TEST_CASE("draw loops", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::atomic_uint64_t frameNumber1 = 0, frameNumber2 = 0;
|
|
std::atomic_uint64_t lastFrameNumber1, lastFrameNumber2;
|
|
auto drawLoop1 = [&frameNumber1](DrawKey, CommandServer*) {
|
|
++frameNumber1;
|
|
};
|
|
auto drawLoop2 = [&frameNumber2](DrawKey, CommandServer*) {
|
|
++frameNumber2;
|
|
};
|
|
DrawKey loopHandle1 = commandQueue->createDrawKey();
|
|
DrawKey loopHandle2 = commandQueue->createDrawKey();
|
|
|
|
commandQueue->runOnce([&](CommandServer*) {
|
|
CHECK(frameNumber1 == 0);
|
|
CHECK(frameNumber2 == 0);
|
|
lastFrameNumber1 = frameNumber1.load();
|
|
lastFrameNumber2 = frameNumber2.load();
|
|
});
|
|
|
|
commandQueue->draw(loopHandle1, drawLoop1);
|
|
commandQueue->draw(loopHandle2, drawLoop2);
|
|
|
|
do
|
|
{
|
|
wait_for_server(commandQueue.get());
|
|
} while (frameNumber1 == lastFrameNumber1 ||
|
|
frameNumber2 == lastFrameNumber2);
|
|
|
|
commandQueue->runOnce([&](CommandServer*) {
|
|
CHECK(frameNumber1 > lastFrameNumber1);
|
|
CHECK(frameNumber2 > lastFrameNumber2);
|
|
});
|
|
|
|
commandQueue->runOnce([&](CommandServer*) {
|
|
lastFrameNumber1 = frameNumber1.load();
|
|
lastFrameNumber2 = frameNumber2.load();
|
|
});
|
|
|
|
commandQueue->draw(loopHandle2, drawLoop2);
|
|
|
|
do
|
|
{
|
|
wait_for_server(commandQueue.get());
|
|
} while (frameNumber2 == lastFrameNumber2);
|
|
commandQueue->runOnce([&](CommandServer*) {
|
|
CHECK(frameNumber1 == lastFrameNumber1);
|
|
CHECK(frameNumber2 > lastFrameNumber2);
|
|
});
|
|
|
|
commandQueue->runOnce([&](CommandServer*) {
|
|
lastFrameNumber1 = frameNumber1.load();
|
|
lastFrameNumber2 = frameNumber2.load();
|
|
});
|
|
|
|
commandQueue->draw(loopHandle1, drawLoop1);
|
|
|
|
do
|
|
{
|
|
wait_for_server(commandQueue.get());
|
|
} while (frameNumber1 == lastFrameNumber1);
|
|
commandQueue->runOnce([&](CommandServer*) {
|
|
CHECK(frameNumber1 > lastFrameNumber1);
|
|
CHECK(frameNumber2 == lastFrameNumber2);
|
|
});
|
|
|
|
commandQueue->runOnce([&](CommandServer*) {
|
|
lastFrameNumber1 = frameNumber1.load();
|
|
lastFrameNumber2 = frameNumber2.load();
|
|
});
|
|
|
|
for (int i = 0; i < 10; ++i)
|
|
{
|
|
wait_for_server(commandQueue.get());
|
|
}
|
|
commandQueue->runOnce([&](CommandServer*) {
|
|
CHECK(frameNumber1 == lastFrameNumber1);
|
|
CHECK(frameNumber2 == lastFrameNumber2);
|
|
});
|
|
|
|
commandQueue->draw(loopHandle1, drawLoop1);
|
|
commandQueue->draw(loopHandle2, drawLoop2);
|
|
|
|
do
|
|
{
|
|
wait_for_server(commandQueue.get());
|
|
} while (frameNumber1 == lastFrameNumber1 ||
|
|
frameNumber2 == lastFrameNumber2);
|
|
commandQueue->runOnce([&](CommandServer*) {
|
|
CHECK(frameNumber1 > lastFrameNumber1);
|
|
CHECK(frameNumber2 > lastFrameNumber2);
|
|
});
|
|
|
|
// Leave the draw loops running; test tearing down the command buffer with
|
|
// active draw loops.
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
TEST_CASE("wait for server race condition", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
auto lambda = [](CommandServer*) {
|
|
std::this_thread::sleep_for(std::chrono::microseconds(10));
|
|
};
|
|
auto drawLambda = [](DrawKey, CommandServer*) {
|
|
std::this_thread::sleep_for(std::chrono::microseconds(10));
|
|
};
|
|
|
|
commandQueue->runOnce(lambda);
|
|
|
|
commandQueue->draw(reinterpret_cast<DrawKey>(0), drawLambda);
|
|
commandQueue->draw(reinterpret_cast<DrawKey>(1), drawLambda);
|
|
commandQueue->draw(reinterpret_cast<DrawKey>(2), drawLambda);
|
|
commandQueue->draw(reinterpret_cast<DrawKey>(3), drawLambda);
|
|
commandQueue->draw(reinterpret_cast<DrawKey>(4), drawLambda);
|
|
|
|
for (size_t i = 0; i < 100; ++i)
|
|
|
|
{
|
|
commandQueue->draw(reinterpret_cast<DrawKey>(i), drawLambda);
|
|
commandQueue->runOnce(lambda);
|
|
}
|
|
for (size_t i = 0; i < 10; ++i)
|
|
|
|
{
|
|
wait_for_server(commandQueue.get());
|
|
}
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
TEST_CASE("stopMesssages command", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::unique_ptr<gpu::RenderContext> nullContext =
|
|
RenderContextNULL::MakeContext();
|
|
CommandServer server(commandQueue, nullContext.get());
|
|
|
|
int test = 0;
|
|
|
|
commandQueue->runOnce([&](CommandServer*) { ++test; });
|
|
commandQueue->testing_commandLoopBreak();
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
commandQueue->runOnce([&](CommandServer*) { ++test; });
|
|
if (i == 5)
|
|
commandQueue->testing_commandLoopBreak();
|
|
}
|
|
|
|
server.processCommands();
|
|
|
|
CHECK(test == 1);
|
|
server.processCommands();
|
|
CHECK(test == 7);
|
|
server.processCommands();
|
|
CHECK(test == 11);
|
|
|
|
commandQueue->disconnect();
|
|
}
|
|
|
|
TEST_CASE("draw happens once per poll", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::unique_ptr<gpu::RenderContext> nullContext =
|
|
RenderContextNULL::MakeContext();
|
|
CommandServer server(commandQueue, nullContext.get());
|
|
int test = 0;
|
|
|
|
auto drawLamda = [&test](DrawKey, CommandServer*) { ++test; };
|
|
|
|
commandQueue->draw(0, drawLamda);
|
|
commandQueue->draw(0, drawLamda);
|
|
commandQueue->draw(0, drawLamda);
|
|
commandQueue->draw(0, drawLamda);
|
|
commandQueue->draw(0, drawLamda);
|
|
|
|
server.processCommands();
|
|
|
|
CHECK(test == 1);
|
|
commandQueue->draw(0, drawLamda);
|
|
server.processCommands();
|
|
CHECK(test == 2);
|
|
server.processCommands();
|
|
CHECK(test == 2);
|
|
|
|
commandQueue->disconnect();
|
|
}
|
|
|
|
TEST_CASE("disconnect", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::unique_ptr<gpu::RenderContext> nullContext =
|
|
RenderContextNULL::MakeContext();
|
|
CommandServer server(commandQueue, nullContext.get());
|
|
CHECK(!server.getWasDisconnected());
|
|
|
|
CHECK(server.processCommands());
|
|
commandQueue->disconnect();
|
|
CHECK(!server.processCommands());
|
|
}
|
|
|
|
TEST_CASE("global asset set / remove", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::unique_ptr<gpu::RenderContext> nullContext =
|
|
RenderContextNULL::MakeContext();
|
|
|
|
CommandServer server(commandQueue, nullContext.get());
|
|
|
|
std::ifstream imageStream("assets/batdude.png", std::ios::binary);
|
|
|
|
auto imageHandle = commandQueue->decodeImage(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(imageStream), {}));
|
|
|
|
commandQueue->addGlobalImageAsset("image", imageHandle);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(server->testing_globalImageNamed("image") == imageHandle);
|
|
});
|
|
|
|
commandQueue->removeGlobalImageAsset("image");
|
|
commandQueue->removeGlobalFontAsset("font");
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(!server->testing_globalImageContains("image"));
|
|
});
|
|
|
|
// should not add nullptr or invalid values to maps
|
|
commandQueue->addGlobalImageAsset("image", nullptr);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(!server->testing_globalImageContains("image"));
|
|
});
|
|
|
|
auto badImageHandle =
|
|
commandQueue->decodeImage(std::vector<uint8_t>(1024, 0));
|
|
|
|
commandQueue->addGlobalImageAsset("image", badImageHandle);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(!server->testing_globalImageContains("image"));
|
|
});
|
|
|
|
commandQueue->removeGlobalImageAsset("blah");
|
|
commandQueue->removeGlobalFontAsset("blah");
|
|
commandQueue->removeGlobalAudioAsset("blah");
|
|
|
|
commandQueue->addGlobalImageAsset("image", imageHandle);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(server->testing_globalImageNamed("image") == imageHandle);
|
|
});
|
|
|
|
commandQueue->deleteImage(imageHandle);
|
|
|
|
// These should be removed automatically
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(!server->testing_globalImageContains("image"));
|
|
});
|
|
|
|
#ifdef WITH_RIVE_AUDIO
|
|
std::ifstream audioStream("assets/audio/what.wav", std::ios::binary);
|
|
|
|
auto audioHandle = commandQueue->decodeAudio(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(audioStream), {}));
|
|
|
|
commandQueue->addGlobalAudioAsset("audio", audioHandle);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(server->testing_globalAudioNamed("audio") == audioHandle);
|
|
});
|
|
|
|
commandQueue->removeGlobalAudioAsset("audio");
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(!server->testing_globalAudioContains("audio"));
|
|
});
|
|
|
|
// should not add nullptr or invalid values to maps
|
|
commandQueue->addGlobalAudioAsset("audio", nullptr);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(!server->testing_globalAudioContains("audio"));
|
|
});
|
|
|
|
auto badAudioHandle =
|
|
commandQueue->decodeAudio(std::vector<uint8_t>(1024, 0));
|
|
|
|
commandQueue->addGlobalAudioAsset("audio", badAudioHandle);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(!server->testing_globalAudioContains("audio"));
|
|
});
|
|
|
|
commandQueue->addGlobalAudioAsset("audio", audioHandle);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(server->testing_globalAudioNamed("audio") == audioHandle);
|
|
});
|
|
|
|
commandQueue->deleteAudio(audioHandle);
|
|
|
|
// These should be removed automatically
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(!server->testing_globalImageContains("image"));
|
|
CHECK(!server->testing_globalFontContains("font"));
|
|
CHECK(!server->testing_globalAudioContains("audio"));
|
|
});
|
|
#endif
|
|
|
|
#ifdef WITH_RIVE_TEXT
|
|
std::ifstream fontStream("assets/fonts/OpenSans-Italic.ttf",
|
|
std::ios::binary);
|
|
|
|
auto fontHandle = commandQueue->decodeFont(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(fontStream), {}));
|
|
|
|
commandQueue->addGlobalFontAsset("font", fontHandle);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(server->testing_globalFontNamed("font") == fontHandle);
|
|
});
|
|
|
|
commandQueue->removeGlobalFontAsset("font");
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(!server->testing_globalFontContains("font"));
|
|
});
|
|
|
|
// should not add nullptr or invalid values to maps
|
|
commandQueue->addGlobalFontAsset("font", nullptr);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(!server->testing_globalFontContains("font"));
|
|
});
|
|
|
|
auto badFontHandle =
|
|
commandQueue->decodeFont(std::vector<uint8_t>(1024, 0));
|
|
|
|
commandQueue->addGlobalFontAsset("font", badFontHandle);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(!server->testing_globalFontContains("font"));
|
|
});
|
|
|
|
commandQueue->addGlobalFontAsset("font", fontHandle);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(server->testing_globalFontNamed("font") == fontHandle);
|
|
});
|
|
|
|
commandQueue->deleteFont(fontHandle);
|
|
|
|
// These should be removed automatically
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(!server->testing_globalFontContains("font"));
|
|
});
|
|
#endif
|
|
|
|
server.processCommands();
|
|
commandQueue->disconnect();
|
|
}
|
|
|
|
TEST_CASE("View Models", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
|
|
auto bviewModel =
|
|
commandQueue->instantiateBlankViewModelInstance(fileHandle, "Test All");
|
|
auto dviewModel =
|
|
commandQueue->instantiateDefaultViewModelInstance(fileHandle,
|
|
"Test All");
|
|
auto nviewModel =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
"Test All",
|
|
"Test Alternate");
|
|
auto neviewModel =
|
|
commandQueue->referenceNestedViewModelInstance(bviewModel,
|
|
"Test Nested");
|
|
commandQueue->insertViewModelInstanceListViewModel(bviewModel,
|
|
"Test List",
|
|
neviewModel,
|
|
0);
|
|
auto listViewModel =
|
|
commandQueue->referenceListViewModelInstance(bviewModel,
|
|
"Test List",
|
|
0);
|
|
commandQueue->runOnce([bviewModel,
|
|
dviewModel,
|
|
nviewModel,
|
|
neviewModel,
|
|
listViewModel](CommandServer* server) {
|
|
CHECK(server->getViewModelInstance(bviewModel) != nullptr);
|
|
CHECK(server->getViewModelInstance(dviewModel) != nullptr);
|
|
CHECK(server->getViewModelInstance(nviewModel) != nullptr);
|
|
CHECK(server->getViewModelInstance(neviewModel) != nullptr);
|
|
CHECK(server->getViewModelInstance(listViewModel) != nullptr);
|
|
CHECK(server->getViewModelInstance(listViewModel) ==
|
|
server->getViewModelInstance(neviewModel));
|
|
auto list =
|
|
server->getViewModelInstance(bviewModel)->propertyList("Test List");
|
|
CHECK(list != nullptr);
|
|
CHECK(list->instanceAt(0).get() ==
|
|
server->getViewModelInstance(listViewModel));
|
|
});
|
|
|
|
commandQueue->removeViewModelInstanceListViewModel(bviewModel,
|
|
"Test List",
|
|
neviewModel);
|
|
|
|
commandQueue->runOnce([bviewModel](CommandServer* server) {
|
|
auto list =
|
|
server->getViewModelInstance(bviewModel)->propertyList("Test List");
|
|
CHECK(list != nullptr);
|
|
CHECK(list->size() == 0);
|
|
});
|
|
|
|
auto bbviewModel =
|
|
commandQueue->instantiateBlankViewModelInstance(fileHandle, "Blah");
|
|
auto bdviewModel =
|
|
commandQueue->instantiateDefaultViewModelInstance(fileHandle, "Blah");
|
|
auto bnviewModel =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
"Blah",
|
|
"Blah");
|
|
auto bnnviewModel =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
"Blah",
|
|
"Test Alternate");
|
|
auto bnbviewModel =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
"Test All",
|
|
"Blah");
|
|
|
|
commandQueue->removeViewModelInstanceListViewModel(bviewModel,
|
|
"Blah",
|
|
neviewModel);
|
|
|
|
commandQueue->removeViewModelInstanceListViewModel(bviewModel,
|
|
"Test List",
|
|
bnbviewModel);
|
|
auto badPathNeviewModel =
|
|
commandQueue->referenceNestedViewModelInstance(bviewModel, "Blah");
|
|
|
|
auto badNeviewModel =
|
|
commandQueue->referenceNestedViewModelInstance(bnnviewModel,
|
|
"Test Nested");
|
|
|
|
auto badListViewModel =
|
|
commandQueue->referenceListViewModelInstance(bviewModel, "Blah", 0);
|
|
|
|
auto badListViewModel2 =
|
|
commandQueue->referenceListViewModelInstance(bviewModel,
|
|
"Test List",
|
|
5);
|
|
|
|
commandQueue->runOnce([bbviewModel,
|
|
bdviewModel,
|
|
bnviewModel,
|
|
bnnviewModel,
|
|
bnbviewModel,
|
|
badPathNeviewModel,
|
|
badNeviewModel,
|
|
badListViewModel,
|
|
badListViewModel2](CommandServer* server) {
|
|
CHECK(server->getViewModelInstance(bbviewModel) == nullptr);
|
|
CHECK(server->getViewModelInstance(bdviewModel) == nullptr);
|
|
CHECK(server->getViewModelInstance(bnviewModel) == nullptr);
|
|
CHECK(server->getViewModelInstance(bnnviewModel) == nullptr);
|
|
CHECK(server->getViewModelInstance(bnbviewModel) == nullptr);
|
|
CHECK(server->getViewModelInstance(badPathNeviewModel) == nullptr);
|
|
CHECK(server->getViewModelInstance(badNeviewModel) == nullptr);
|
|
CHECK(server->getViewModelInstance(badListViewModel) == nullptr);
|
|
CHECK(server->getViewModelInstance(badListViewModel2) == nullptr);
|
|
});
|
|
|
|
auto artboard =
|
|
commandQueue->instantiateArtboardNamed(fileHandle, "Test Artboard");
|
|
|
|
auto abviewModel =
|
|
commandQueue->instantiateBlankViewModelInstance(fileHandle, artboard);
|
|
auto adviewModel =
|
|
commandQueue->instantiateDefaultViewModelInstance(fileHandle, artboard);
|
|
auto anviewModel =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
artboard,
|
|
"Test Alternate");
|
|
|
|
commandQueue->runOnce(
|
|
[abviewModel, adviewModel, anviewModel](CommandServer* server) {
|
|
CHECK(server->getViewModelInstance(abviewModel) != nullptr);
|
|
CHECK(server->getViewModelInstance(adviewModel) != nullptr);
|
|
CHECK(server->getViewModelInstance(anviewModel) != nullptr);
|
|
});
|
|
|
|
auto badArtboard =
|
|
commandQueue->instantiateArtboardNamed(fileHandle, "Blah");
|
|
|
|
auto babviewModel =
|
|
commandQueue->instantiateBlankViewModelInstance(fileHandle,
|
|
badArtboard);
|
|
auto badviewModel =
|
|
commandQueue->instantiateDefaultViewModelInstance(fileHandle,
|
|
badArtboard);
|
|
auto banviewModel =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
badArtboard,
|
|
"Test Alternate");
|
|
|
|
commandQueue->runOnce(
|
|
[babviewModel, badviewModel, banviewModel](CommandServer* server) {
|
|
CHECK(server->getViewModelInstance(babviewModel) == nullptr);
|
|
CHECK(server->getViewModelInstance(badviewModel) == nullptr);
|
|
CHECK(server->getViewModelInstance(banviewModel) == nullptr);
|
|
});
|
|
|
|
commandQueue->deleteViewModelInstance(badNeviewModel);
|
|
commandQueue->deleteViewModelInstance(bviewModel);
|
|
|
|
commandQueue->runOnce([neviewModel](CommandServer* server) {
|
|
CHECK(server->getViewModelInstance(neviewModel) != nullptr);
|
|
});
|
|
|
|
commandQueue->deleteViewModelInstance(neviewModel);
|
|
commandQueue->runOnce([neviewModel](CommandServer* server) {
|
|
CHECK(server->getViewModelInstance(neviewModel) == nullptr);
|
|
});
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class ViewModelListedListenerCallback : public CommandQueue::FileListener
|
|
{
|
|
public:
|
|
virtual void onViewModelsListed(
|
|
const FileHandle fileHandle,
|
|
uint64_t requestId,
|
|
std::vector<std::string> viewModelNames) override
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(m_fileHandle == fileHandle);
|
|
CHECK(viewModelNames.size() == std::size(m_expectedViewModelNames));
|
|
|
|
for (int i = 0; i < std::size(m_expectedViewModelNames); ++i)
|
|
{
|
|
CHECK(viewModelNames[i] == m_expectedViewModelNames[i]);
|
|
}
|
|
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
std::array<std::string, 6> m_expectedViewModelNames = {"Empty VM",
|
|
"Test All",
|
|
"Nested VM",
|
|
"State Transition",
|
|
"Alternate VM",
|
|
"Test Slash"};
|
|
bool m_hasCallback = false;
|
|
FileHandle m_fileHandle = RIVE_NULL_HANDLE;
|
|
uint64_t m_requestId;
|
|
};
|
|
|
|
TEST_CASE("View Model Listed Listener", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
{
|
|
ViewModelListedListenerCallback listener;
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv",
|
|
std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
&listener);
|
|
|
|
listener.m_requestId = 2;
|
|
listener.m_fileHandle = fileHandle;
|
|
|
|
commandQueue->requestViewModelNames(fileHandle, listener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(listener.m_hasCallback);
|
|
}
|
|
|
|
{
|
|
ViewModelListedListenerCallback listener;
|
|
|
|
FileHandle fileHandle =
|
|
commandQueue->loadFile(std::vector<uint8_t>(1024 * 1024, {}),
|
|
&listener);
|
|
|
|
listener.m_requestId = 2;
|
|
listener.m_fileHandle = fileHandle;
|
|
|
|
commandQueue->requestViewModelNames(fileHandle, listener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(!listener.m_hasCallback);
|
|
}
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class TestViewModelFileListener : public CommandQueue::FileListener
|
|
{
|
|
public:
|
|
virtual void onViewModelInstanceNamesListed(
|
|
const FileHandle handle,
|
|
uint64_t requestId,
|
|
std::string viewModelName,
|
|
std::vector<std::string> instanceNames) override
|
|
{
|
|
CHECK(requestId == m_instanceRequestId);
|
|
CHECK(m_handle == handle);
|
|
CHECK(m_viewModelName == viewModelName);
|
|
CHECK(instanceNames.size() == std::size(m_expectedInstanceNames));
|
|
|
|
for (int i = 0; i < std::size(m_expectedInstanceNames); ++i)
|
|
{
|
|
CHECK(instanceNames[i] == m_expectedInstanceNames[i]);
|
|
}
|
|
|
|
m_hasInstanceCallback = true;
|
|
}
|
|
|
|
virtual void onViewModelPropertiesListed(
|
|
const FileHandle handle,
|
|
uint64_t requestId,
|
|
std::string viewModelName,
|
|
std::vector<CommandQueue::FileListener::ViewModelPropertyData>
|
|
properties) override
|
|
{
|
|
CHECK(requestId == m_propertyRequestId);
|
|
CHECK(m_handle == handle);
|
|
CHECK(m_viewModelName == viewModelName);
|
|
CHECK(properties.size() == std::size(m_expectedProperties));
|
|
|
|
for (int i = 0; i < std::size(m_expectedProperties); ++i)
|
|
{
|
|
CHECK(properties[i] == m_expectedProperties[i]);
|
|
}
|
|
|
|
m_hasPropertyCallback = true;
|
|
}
|
|
|
|
std::array<std::string, 2> m_expectedInstanceNames = {"Test Default",
|
|
"Test Alternate"};
|
|
std::array<CommandQueue::FileListener::ViewModelPropertyData, 10>
|
|
m_expectedProperties = {
|
|
CommandQueue::FileListener::ViewModelPropertyData{
|
|
DataType::artboard,
|
|
"Test Artboard"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{DataType::list,
|
|
"Test List"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{
|
|
DataType::assetImage,
|
|
"Test Image"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{DataType::number,
|
|
"Test Num"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{DataType::string,
|
|
"Test String"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{
|
|
DataType::enumType,
|
|
"Test Enum",
|
|
"Test Enum Values"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{DataType::boolean,
|
|
"Test Bool"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{DataType::color,
|
|
"Test Color"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{DataType::trigger,
|
|
"Test Trigger"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{
|
|
DataType::viewModel,
|
|
"Test Nested"}};
|
|
|
|
bool m_hasInstanceCallback = false;
|
|
bool m_hasPropertyCallback = false;
|
|
FileHandle m_handle = RIVE_NULL_HANDLE;
|
|
std::string m_viewModelName = "Test All";
|
|
uint64_t m_instanceRequestId = 2;
|
|
uint64_t m_propertyRequestId = 3;
|
|
};
|
|
|
|
TEST_CASE("View Model Listener", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
{
|
|
TestViewModelFileListener listener;
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv",
|
|
std::ios::binary);
|
|
listener.m_handle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
&listener);
|
|
|
|
commandQueue->requestViewModelInstanceNames(
|
|
listener.m_handle,
|
|
listener.m_viewModelName,
|
|
listener.m_instanceRequestId);
|
|
commandQueue->requestViewModelPropertyDefinitions(
|
|
listener.m_handle,
|
|
listener.m_viewModelName,
|
|
listener.m_propertyRequestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(listener.m_hasInstanceCallback);
|
|
CHECK(listener.m_hasPropertyCallback);
|
|
}
|
|
|
|
{
|
|
TestViewModelFileListener listener;
|
|
|
|
listener.m_handle =
|
|
commandQueue->loadFile(std::vector<uint8_t>(1024 * 1024, {}));
|
|
|
|
commandQueue->requestViewModelInstanceNames(
|
|
listener.m_handle,
|
|
listener.m_viewModelName,
|
|
listener.m_instanceRequestId);
|
|
commandQueue->requestViewModelPropertyDefinitions(
|
|
listener.m_handle,
|
|
listener.m_viewModelName,
|
|
listener.m_propertyRequestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(!listener.m_hasInstanceCallback);
|
|
CHECK(!listener.m_hasPropertyCallback);
|
|
}
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class TestViewModelInstanceListener
|
|
: public CommandQueue::ViewModelInstanceListener
|
|
{
|
|
public:
|
|
virtual void onViewModelDeleted(const ViewModelInstanceHandle handle,
|
|
uint64_t requestId) override
|
|
{
|
|
CHECK(requestId == m_deleteRequestId);
|
|
CHECK(m_handle == handle);
|
|
m_hasDeleteCallback = true;
|
|
}
|
|
|
|
bool m_hasDeleteCallback = false;
|
|
|
|
ViewModelInstanceHandle m_handle = RIVE_NULL_HANDLE;
|
|
uint64_t m_deleteRequestId = std::rand();
|
|
};
|
|
|
|
TEST_CASE("View Model Instance Listener", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
TestViewModelInstanceListener bListener;
|
|
TestViewModelInstanceListener dListener;
|
|
TestViewModelInstanceListener nListener;
|
|
|
|
TestViewModelInstanceListener badListener;
|
|
|
|
TestViewModelInstanceListener aListener;
|
|
TestViewModelInstanceListener adListener;
|
|
TestViewModelInstanceListener anListener;
|
|
TestViewModelInstanceListener badAListener;
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
|
|
bListener.m_handle =
|
|
commandQueue->instantiateBlankViewModelInstance(fileHandle,
|
|
"Test All",
|
|
&bListener);
|
|
dListener.m_handle =
|
|
commandQueue->instantiateDefaultViewModelInstance(fileHandle,
|
|
"Test All",
|
|
&dListener);
|
|
nListener.m_handle =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
"Test All",
|
|
"Test Alternate",
|
|
&nListener);
|
|
|
|
badListener.m_handle =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
"Blah",
|
|
"Blah",
|
|
&badListener);
|
|
|
|
auto artboard =
|
|
commandQueue->instantiateArtboardNamed(fileHandle, "Test Artboard");
|
|
|
|
auto badArtboard =
|
|
commandQueue->instantiateArtboardNamed(fileHandle, "Blah");
|
|
|
|
aListener.m_handle =
|
|
commandQueue->instantiateBlankViewModelInstance(fileHandle,
|
|
artboard,
|
|
&aListener);
|
|
adListener.m_handle =
|
|
commandQueue->instantiateDefaultViewModelInstance(fileHandle,
|
|
artboard,
|
|
&adListener);
|
|
anListener.m_handle =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
artboard,
|
|
"Test Alternate",
|
|
&anListener);
|
|
|
|
badAListener.m_handle =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
badArtboard,
|
|
"Test Alternate",
|
|
&badAListener);
|
|
|
|
commandQueue->deleteViewModelInstance(bListener.m_handle,
|
|
bListener.m_deleteRequestId);
|
|
commandQueue->deleteViewModelInstance(dListener.m_handle,
|
|
dListener.m_deleteRequestId);
|
|
commandQueue->deleteViewModelInstance(nListener.m_handle,
|
|
nListener.m_deleteRequestId);
|
|
commandQueue->deleteViewModelInstance(badListener.m_handle,
|
|
badListener.m_deleteRequestId);
|
|
|
|
commandQueue->deleteViewModelInstance(aListener.m_handle,
|
|
aListener.m_deleteRequestId);
|
|
commandQueue->deleteViewModelInstance(adListener.m_handle,
|
|
adListener.m_deleteRequestId);
|
|
commandQueue->deleteViewModelInstance(anListener.m_handle,
|
|
anListener.m_deleteRequestId);
|
|
commandQueue->deleteViewModelInstance(badAListener.m_handle,
|
|
badAListener.m_deleteRequestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(bListener.m_hasDeleteCallback);
|
|
CHECK(dListener.m_hasDeleteCallback);
|
|
CHECK(nListener.m_hasDeleteCallback);
|
|
CHECK(badListener.m_hasDeleteCallback);
|
|
|
|
CHECK(aListener.m_hasDeleteCallback);
|
|
CHECK(adListener.m_hasDeleteCallback);
|
|
CHECK(anListener.m_hasDeleteCallback);
|
|
CHECK(badAListener.m_hasDeleteCallback);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
TEST_CASE("External Resources", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
rcp<RenderImage> externalImage = nullptr;
|
|
rcp<AudioSource> externalAudio = nullptr;
|
|
rcp<Font> externalFont = nullptr;
|
|
|
|
commandQueue->runOnce([&externalImage, &externalAudio, &externalFont](
|
|
CommandServer* server) {
|
|
std::ifstream imageStream("assets/batdude.png", std::ios::binary);
|
|
std::vector<uint8_t> imageStreamData(
|
|
std::istreambuf_iterator<char>(imageStream),
|
|
{});
|
|
|
|
std::ifstream audioStream("assets/audio/what.wav", std::ios::binary);
|
|
std::vector<uint8_t> audioStreamData(
|
|
std::istreambuf_iterator<char>(audioStream),
|
|
{});
|
|
|
|
std::ifstream fontStream("assets/fonts/OpenSans-Italic.ttf",
|
|
std::ios::binary);
|
|
std::vector<uint8_t> fontStreamData(
|
|
std::istreambuf_iterator<char>(fontStream),
|
|
{});
|
|
|
|
auto factory = server->factory();
|
|
|
|
externalImage = factory->decodeImage(imageStreamData);
|
|
externalAudio = factory->decodeAudio(audioStreamData);
|
|
externalFont = factory->decodeFont(fontStreamData);
|
|
});
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
CHECK(externalImage);
|
|
CHECK(externalAudio);
|
|
CHECK(externalFont);
|
|
|
|
RenderImageHandle externalImageHandle =
|
|
commandQueue->addExternalImage(externalImage);
|
|
AudioSourceHandle externalAudioHandle =
|
|
commandQueue->addExternalAudio(externalAudio);
|
|
FontHandle externalFontHandle = commandQueue->addExternalFont(externalFont);
|
|
|
|
commandQueue->runOnce([externalImageHandle,
|
|
externalImage,
|
|
externalAudioHandle,
|
|
externalAudio,
|
|
externalFontHandle,
|
|
externalFont](CommandServer* server) {
|
|
auto image = server->getImage(externalImageHandle);
|
|
CHECK(image == externalImage.get());
|
|
auto audio = server->getAudioSource(externalAudioHandle);
|
|
CHECK(audio == externalAudio.get());
|
|
auto font = server->getFont(externalFontHandle);
|
|
CHECK(font == externalFont.get());
|
|
});
|
|
|
|
commandQueue->deleteImage(externalImageHandle);
|
|
commandQueue->deleteAudio(externalAudioHandle);
|
|
commandQueue->deleteFont(externalFontHandle);
|
|
|
|
commandQueue->runOnce([externalImageHandle,
|
|
externalAudioHandle,
|
|
externalFontHandle](CommandServer* server) {
|
|
auto image = server->getImage(externalImageHandle);
|
|
CHECK(image == nullptr);
|
|
auto audio = server->getAudioSource(externalAudioHandle);
|
|
CHECK(audio == nullptr);
|
|
auto font = server->getFont(externalFontHandle);
|
|
CHECK(font == nullptr);
|
|
});
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
TEST_CASE("RenderImage", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/batdude.png", std::ios::binary);
|
|
RenderImageHandle imageHandle = commandQueue->decodeImage(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
RenderImageHandle badImageHandle =
|
|
commandQueue->decodeImage(std::vector<uint8_t>(1024, {}));
|
|
|
|
commandQueue->runOnce([imageHandle, badImageHandle](CommandServer* server) {
|
|
auto image = server->getImage(imageHandle);
|
|
CHECK(image != nullptr);
|
|
auto badImage = server->getImage(badImageHandle);
|
|
CHECK(badImage == nullptr);
|
|
});
|
|
|
|
commandQueue->deleteImage(imageHandle);
|
|
commandQueue->deleteImage(badImageHandle);
|
|
|
|
commandQueue->runOnce([imageHandle, badImageHandle](CommandServer* server) {
|
|
auto image = server->getImage(imageHandle);
|
|
CHECK(image == nullptr);
|
|
auto badImage = server->getImage(badImageHandle);
|
|
CHECK(badImage == nullptr);
|
|
});
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
#ifdef WITH_RIVE_AUDIO
|
|
class AudioSourceDeletedListener : public CommandQueue::AudioSourceListener
|
|
{
|
|
public:
|
|
virtual void onAudioSourceDeleted(const AudioSourceHandle handle,
|
|
uint64_t requestId)
|
|
{
|
|
CHECK(m_handle == handle);
|
|
CHECK(m_requestId == requestId);
|
|
CHECK(!m_hasCallback);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
AudioSourceHandle m_handle;
|
|
bool m_hasCallback = false;
|
|
uint64_t m_requestId = 0x10;
|
|
};
|
|
|
|
TEST_CASE("AudioSource", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/audio/what.wav", std::ios::binary);
|
|
|
|
AudioSourceDeletedListener listener;
|
|
|
|
AudioSourceHandle audioHandle = commandQueue->decodeAudio(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
&listener,
|
|
10);
|
|
listener.m_handle = audioHandle;
|
|
AudioSourceHandle badAudioHandle =
|
|
commandQueue->decodeAudio(std::vector<uint8_t>(1024, {}));
|
|
|
|
commandQueue->runOnce([audioHandle, badAudioHandle](CommandServer* server) {
|
|
auto audio = server->getAudioSource(audioHandle);
|
|
CHECK(audio != nullptr);
|
|
auto badAudio = server->getAudioSource(badAudioHandle);
|
|
CHECK(badAudio == nullptr);
|
|
});
|
|
|
|
commandQueue->deleteAudio(audioHandle, listener.m_requestId);
|
|
commandQueue->deleteAudio(badAudioHandle);
|
|
|
|
commandQueue->runOnce([audioHandle, badAudioHandle](CommandServer* server) {
|
|
auto audio = server->getAudioSource(audioHandle);
|
|
CHECK(audio == nullptr);
|
|
auto badAudio = server->getAudioSource(badAudioHandle);
|
|
CHECK(badAudio == nullptr);
|
|
});
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(listener.m_hasCallback);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
#endif
|
|
|
|
#if WITH_RIVE_TEXT
|
|
|
|
class FontDeletedListener : public CommandQueue::FontListener
|
|
{
|
|
public:
|
|
virtual void onFontDeleted(const FontHandle handle, uint64_t requestId)
|
|
{
|
|
CHECK(m_handle == handle);
|
|
CHECK(m_requestId == requestId);
|
|
CHECK(!m_hasCallback);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
FontHandle m_handle;
|
|
bool m_hasCallback = false;
|
|
uint64_t m_requestId = 0x10;
|
|
};
|
|
|
|
TEST_CASE("Font", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
FontDeletedListener listener;
|
|
|
|
std::ifstream stream("assets/fonts/OpenSans-Italic.ttf", std::ios::binary);
|
|
FontHandle fontHandle = commandQueue->decodeFont(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
&listener,
|
|
10);
|
|
listener.m_handle = fontHandle;
|
|
FontHandle badFontHandle =
|
|
commandQueue->decodeFont(std::vector<uint8_t>(1024, {}));
|
|
|
|
commandQueue->runOnce([fontHandle, badFontHandle](CommandServer* server) {
|
|
auto font = server->getFont(fontHandle);
|
|
CHECK(font != nullptr);
|
|
auto badFont = server->getFont(badFontHandle);
|
|
CHECK(badFont == nullptr);
|
|
});
|
|
|
|
commandQueue->deleteFont(fontHandle, listener.m_requestId);
|
|
commandQueue->deleteFont(badFontHandle);
|
|
|
|
commandQueue->runOnce([fontHandle, badFontHandle](CommandServer* server) {
|
|
auto font = server->getFont(fontHandle);
|
|
CHECK(font == nullptr);
|
|
auto badFont = server->getFont(badFontHandle);
|
|
CHECK(badFont == nullptr);
|
|
});
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(listener.m_hasCallback);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
#endif
|
|
|
|
namespace rive
|
|
{
|
|
bool operator==(const CommandQueue::ViewModelInstanceData& l,
|
|
const CommandQueue::ViewModelInstanceData& r)
|
|
|
|
{
|
|
bool ret = l.metaData == r.metaData;
|
|
switch (l.metaData.type)
|
|
{
|
|
case DataType::boolean:
|
|
ret &= l.boolValue == r.boolValue;
|
|
break;
|
|
case DataType::number:
|
|
ret &= l.numberValue == r.numberValue;
|
|
break;
|
|
case DataType::color:
|
|
ret &= l.colorValue == r.colorValue;
|
|
break;
|
|
case DataType::string:
|
|
case DataType::enumType:
|
|
ret &= l.stringValue == r.stringValue;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
} // namespace rive
|
|
|
|
class ViewModelPropertyListener : public CommandQueue::ViewModelInstanceListener
|
|
{
|
|
public:
|
|
virtual void onViewModelInstanceError(const ViewModelInstanceHandle handle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(error.size());
|
|
++m_receivedErrors;
|
|
}
|
|
virtual void onViewModelDeleted(const ViewModelInstanceHandle handle,
|
|
uint64_t requestId) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(!n_wasDeleted);
|
|
n_wasDeleted = true;
|
|
}
|
|
|
|
virtual void onViewModelDataReceived(
|
|
const ViewModelInstanceHandle handle,
|
|
uint64_t requestId,
|
|
CommandQueue::ViewModelInstanceData data) override
|
|
{
|
|
// the callback order should be garunteed.
|
|
// so getting these in the order they are requested should work
|
|
CHECK(m_expectedData.size());
|
|
CHECK(m_expectedRequestIds.size());
|
|
auto expectedData = m_expectedData.front();
|
|
m_expectedData.pop_front();
|
|
auto expectedRequestId = m_expectedRequestIds.front();
|
|
m_expectedRequestIds.pop_front();
|
|
|
|
CHECK(handle == m_handle);
|
|
CHECK(data == expectedData);
|
|
CHECK(requestId == expectedRequestId);
|
|
}
|
|
|
|
void pushExpectation(CommandQueue* queue, std::string name, float value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->setViewModelInstanceNumber(m_handle, name, value, m_requestIdx);
|
|
queue->runOnce([handle = m_handle, name, value](CommandServer* server) {
|
|
auto instance = server->getViewModelInstance(handle);
|
|
CHECK(instance != nullptr);
|
|
auto property = instance->propertyNumber(name);
|
|
CHECK(property != nullptr);
|
|
CHECK(property->value() == value);
|
|
});
|
|
m_expectedData.push_back(
|
|
{.metaData = {DataType::number, name}, .numberValue = value});
|
|
m_expectedRequestIds.push_back(m_requestIdx);
|
|
queue->requestViewModelInstanceNumber(m_handle, name, m_requestIdx);
|
|
}
|
|
|
|
void pushExpectation(CommandQueue* queue, std::string name, ColorInt value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->setViewModelInstanceColor(m_handle, name, value, m_requestIdx);
|
|
queue->runOnce([handle = m_handle, name, value](CommandServer* server) {
|
|
auto instance = server->getViewModelInstance(handle);
|
|
CHECK(instance != nullptr);
|
|
auto property = instance->propertyColor(name);
|
|
CHECK(property != nullptr);
|
|
CHECK(property->value() == value);
|
|
});
|
|
m_expectedData.push_back(
|
|
{.metaData = {DataType::color, name}, .colorValue = value});
|
|
m_expectedRequestIds.push_back(m_requestIdx);
|
|
queue->requestViewModelInstanceColor(m_handle, name, m_requestIdx);
|
|
}
|
|
|
|
void pushExpectation(CommandQueue* queue, std::string name, bool value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->setViewModelInstanceBool(m_handle, name, value, m_requestIdx);
|
|
queue->runOnce([handle = m_handle, name, value](CommandServer* server) {
|
|
auto instance = server->getViewModelInstance(handle);
|
|
CHECK(instance != nullptr);
|
|
auto property = instance->propertyBoolean(name);
|
|
CHECK(property != nullptr);
|
|
CHECK(property->value() == value);
|
|
});
|
|
m_expectedData.push_back(
|
|
{.metaData = {DataType::boolean, name}, .boolValue = value});
|
|
m_expectedRequestIds.push_back(m_requestIdx);
|
|
queue->requestViewModelInstanceBool(m_handle, name, m_requestIdx);
|
|
}
|
|
|
|
void pushExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
ViewModelInstanceHandle value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->setViewModelInstanceNestedViewModel(m_handle,
|
|
name,
|
|
value,
|
|
m_requestIdx);
|
|
queue->runOnce([handle = m_handle, name, value](CommandServer* server) {
|
|
auto instance = server->getViewModelInstance(handle);
|
|
auto nested = server->getViewModelInstance(value);
|
|
CHECK(instance != nullptr);
|
|
CHECK(nested != nullptr);
|
|
auto property = instance->propertyViewModel(name);
|
|
CHECK(property != nullptr);
|
|
CHECK(property.get() == nested);
|
|
});
|
|
|
|
// There is no requesting for nested view models
|
|
}
|
|
|
|
void pushStringExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
std::string value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->setViewModelInstanceString(m_handle, name, value, m_requestIdx);
|
|
queue->runOnce([handle = m_handle, name, value](CommandServer* server) {
|
|
auto instance = server->getViewModelInstance(handle);
|
|
CHECK(instance != nullptr);
|
|
auto property = instance->propertyString(name);
|
|
CHECK(property != nullptr);
|
|
CHECK(property->value() == value);
|
|
});
|
|
m_expectedData.push_back(
|
|
{.metaData = {DataType::string, name}, .stringValue = value});
|
|
m_expectedRequestIds.push_back(m_requestIdx);
|
|
queue->requestViewModelInstanceString(m_handle, name, m_requestIdx);
|
|
}
|
|
|
|
void pushEnumExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
std::string value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->setViewModelInstanceEnum(m_handle, name, value, m_requestIdx);
|
|
queue->runOnce([handle = m_handle, name, value](CommandServer* server) {
|
|
auto instance = server->getViewModelInstance(handle);
|
|
CHECK(instance != nullptr);
|
|
auto property = instance->propertyEnum(name);
|
|
CHECK(property != nullptr);
|
|
CHECK(property->value() == value);
|
|
});
|
|
m_expectedData.push_back(
|
|
{.metaData = {DataType::enumType, name}, .stringValue = value});
|
|
m_expectedRequestIds.push_back(m_requestIdx);
|
|
queue->requestViewModelInstanceEnum(m_handle, name, m_requestIdx);
|
|
}
|
|
|
|
void pushBadExpectation(CommandQueue* queue, std::string name, float value)
|
|
{
|
|
queue->setViewModelInstanceNumber(m_handle, name, value, m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
void pushBadExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
ColorInt value)
|
|
{
|
|
queue->setViewModelInstanceColor(m_handle, name, value, m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
void pushBadExpectation(CommandQueue* queue, std::string name, bool value)
|
|
{
|
|
queue->setViewModelInstanceBool(m_handle, name, value, m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
void pushBadExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
ViewModelInstanceHandle value)
|
|
{
|
|
queue->setViewModelInstanceNestedViewModel(m_handle,
|
|
name,
|
|
value,
|
|
m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
void pushBadStringExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
std::string value)
|
|
{
|
|
queue->setViewModelInstanceString(m_handle, name, value, m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
void pushBadEnumExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
std::string value)
|
|
{
|
|
queue->setViewModelInstanceEnum(m_handle, name, value, m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
// one data and id per callback
|
|
std::deque<CommandQueue::ViewModelInstanceData> m_expectedData;
|
|
std::deque<uint64_t> m_expectedRequestIds;
|
|
// all of the handles should be the same
|
|
ViewModelInstanceHandle m_handle;
|
|
bool n_wasDeleted = false;
|
|
|
|
uint64_t m_requestIdx = 1;
|
|
|
|
uint64_t m_expectedErrors = 0;
|
|
uint64_t m_receivedErrors = 0;
|
|
};
|
|
|
|
TEST_CASE("View Model Property Set/Get", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
|
|
ViewModelPropertyListener tester;
|
|
|
|
auto artboardHandle = commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
tester.m_handle =
|
|
commandQueue->instantiateDefaultViewModelInstance(fileHandle,
|
|
artboardHandle,
|
|
&tester);
|
|
auto blankHandle =
|
|
commandQueue->instantiateBlankViewModelInstance(fileHandle,
|
|
"Nested VM");
|
|
auto alternateHandle =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
"Nested VM",
|
|
"Alternate Nested");
|
|
|
|
tester.pushExpectation(commandQueue.get(), "Test Bool", true);
|
|
tester.pushExpectation(commandQueue.get(), "Test Num", 10.0f);
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Nested/Nested Number",
|
|
10.0f);
|
|
tester.pushExpectation(commandQueue.get(), "Test Nested", blankHandle);
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Nested/Nested Number",
|
|
10.0f);
|
|
commandQueue->runOnce([rootHandle = tester.m_handle,
|
|
blankHandle](CommandServer* server) {
|
|
auto root = server->getViewModelInstance(rootHandle);
|
|
auto nested = server->getViewModelInstance(blankHandle);
|
|
CHECK(root != nullptr);
|
|
CHECK(nested != nullptr);
|
|
auto rootProperty = root->propertyNumber("Test Nested/Nested Number");
|
|
CHECK(rootProperty != nullptr);
|
|
CHECK(rootProperty->value() == 10.0f);
|
|
auto nestedProperty = nested->propertyNumber("Nested Number");
|
|
CHECK(nestedProperty != nullptr);
|
|
CHECK(nestedProperty->value() == 10.0f);
|
|
});
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Color",
|
|
rive::colorARGB(255, 255, 0, 0));
|
|
tester.pushEnumExpectation(commandQueue.get(), "Test Enum", "Value 2");
|
|
tester.pushStringExpectation(commandQueue.get(),
|
|
"Test String",
|
|
"Some String");
|
|
|
|
// Images don't have a "get" equivalent so we test it with a run once
|
|
// directly.
|
|
std::ifstream imageStream("assets/batdude.png", std::ios::binary);
|
|
auto imageHandle = commandQueue->decodeImage(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(imageStream), {}));
|
|
commandQueue->setViewModelInstanceImage(tester.m_handle,
|
|
"Test Image",
|
|
imageHandle);
|
|
|
|
commandQueue->runOnce(
|
|
[imageHandle, handle = tester.m_handle](CommandServer* server) {
|
|
auto image = server->getImage(imageHandle);
|
|
CHECK(image != nullptr);
|
|
auto viewModel = server->getViewModelInstance(handle);
|
|
CHECK(viewModel != nullptr);
|
|
auto imageProperty = viewModel->propertyImage("Test Image");
|
|
CHECK(imageProperty != nullptr);
|
|
CHECK(imageProperty->testing_value() == image);
|
|
});
|
|
|
|
// Same for artboards.
|
|
auto bindableArtboardHandle =
|
|
commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
commandQueue->setViewModelInstanceArtboard(tester.m_handle,
|
|
"Test Artboard",
|
|
bindableArtboardHandle);
|
|
|
|
commandQueue->runOnce([bindableArtboardHandle,
|
|
handle = tester.m_handle](CommandServer* server) {
|
|
auto bindableArtboard =
|
|
server->getBindableArtboard(bindableArtboardHandle);
|
|
CHECK(bindableArtboard != nullptr);
|
|
auto viewModel = server->getViewModelInstance(handle);
|
|
CHECK(viewModel != nullptr);
|
|
auto artboardProperty = viewModel->propertyArtboard("Test Artboard");
|
|
CHECK(artboardProperty != nullptr);
|
|
CHECK(artboardProperty->testing_value() == bindableArtboard);
|
|
});
|
|
commandQueue->deleteArtboard(bindableArtboardHandle);
|
|
|
|
auto badImageHandle =
|
|
commandQueue->decodeImage(std::vector<uint8_t>(1024 * 1024, {}));
|
|
commandQueue->setViewModelInstanceImage(tester.m_handle,
|
|
"Test Image",
|
|
badImageHandle);
|
|
++tester.m_expectedErrors;
|
|
|
|
auto badArtboardHandle =
|
|
commandQueue->instantiateArtboardNamed(fileHandle, "Blah");
|
|
commandQueue->setViewModelInstanceArtboard(tester.m_handle,
|
|
"Test Artboard",
|
|
badArtboardHandle);
|
|
++tester.m_expectedErrors;
|
|
|
|
commandQueue->runOnce([imageHandle,
|
|
badImageHandle,
|
|
handle = tester.m_handle](CommandServer* server) {
|
|
auto image = server->getImage(imageHandle);
|
|
auto badImage = server->getImage(badImageHandle);
|
|
CHECK(image != nullptr);
|
|
CHECK(badImage == nullptr);
|
|
auto viewModel = server->getViewModelInstance(handle);
|
|
CHECK(viewModel != nullptr);
|
|
auto imageProperty = viewModel->propertyImage("Test Image");
|
|
CHECK(imageProperty != nullptr);
|
|
CHECK(imageProperty->testing_value() == image);
|
|
});
|
|
|
|
commandQueue->setViewModelInstanceImage(tester.m_handle,
|
|
"Blah",
|
|
imageHandle);
|
|
// Account for bad image request.
|
|
++tester.m_expectedErrors;
|
|
|
|
commandQueue->setViewModelInstanceArtboard(tester.m_handle,
|
|
"Blah",
|
|
artboardHandle);
|
|
// Account for bad artboard request.
|
|
++tester.m_expectedErrors;
|
|
|
|
commandQueue->runOnce(
|
|
[imageHandle, handle = tester.m_handle](CommandServer* server) {
|
|
auto image = server->getImage(imageHandle);
|
|
CHECK(image != nullptr);
|
|
auto viewModel = server->getViewModelInstance(handle);
|
|
CHECK(viewModel != nullptr);
|
|
auto imageProperty = viewModel->propertyImage("Test Image");
|
|
CHECK(imageProperty != nullptr);
|
|
CHECK(imageProperty->testing_value() == image);
|
|
});
|
|
|
|
// We should set / get in order as it goes through the list.
|
|
for (int i = 0; i < 10; ++i)
|
|
{
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Bool",
|
|
static_cast<bool>(i % 2));
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Num",
|
|
static_cast<float>(i));
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Nested",
|
|
i % 2 ? blankHandle : alternateHandle);
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Color",
|
|
rive::colorARGB(i, i, i, i));
|
|
tester.pushEnumExpectation(commandQueue.get(),
|
|
"Test Enum",
|
|
i % 2 ? "Value 2" : "Value 1");
|
|
tester.pushStringExpectation(commandQueue.get(),
|
|
"Test String",
|
|
std::to_string(i));
|
|
}
|
|
|
|
// Bad values
|
|
|
|
commandQueue->deleteViewModelInstance(blankHandle);
|
|
commandQueue->deleteViewModelInstance(alternateHandle);
|
|
|
|
// Good property path bad value
|
|
tester.pushBadEnumExpectation(commandQueue.get(), "Test Enum", "Blah");
|
|
tester.pushBadExpectation(commandQueue.get(), "Test Nested", blankHandle);
|
|
|
|
// Bad everything
|
|
tester.pushBadExpectation(commandQueue.get(), "Blah", true);
|
|
tester.pushBadExpectation(commandQueue.get(), "Blah", 10.0f);
|
|
tester.pushBadExpectation(commandQueue.get(), "Blah", alternateHandle);
|
|
tester.pushBadExpectation(commandQueue.get(),
|
|
"Blah",
|
|
rive::colorARGB(255, 255, 0, 0));
|
|
tester.pushBadEnumExpectation(commandQueue.get(), "Blah", "Value 2");
|
|
tester.pushBadStringExpectation(commandQueue.get(), "Blah", "Some String");
|
|
|
|
// Delete the instance, the callback should continue fine and a delete
|
|
// should be received
|
|
commandQueue->deleteViewModelInstance(tester.m_handle);
|
|
|
|
// Call sets on deleted handle
|
|
tester.pushBadExpectation(commandQueue.get(), "Test Bool", true);
|
|
tester.pushBadExpectation(commandQueue.get(), "Test Num", 10.0f);
|
|
tester.pushBadExpectation(commandQueue.get(),
|
|
"Test Color",
|
|
rive::colorARGB(255, 255, 0, 0));
|
|
tester.pushBadEnumExpectation(commandQueue.get(), "Test Enum", "Value 2");
|
|
tester.pushBadStringExpectation(commandQueue.get(),
|
|
"Test String",
|
|
"Some String");
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
// We expect there should be no expected values left
|
|
CHECK(tester.m_expectedData.size() == 0);
|
|
CHECK(tester.m_expectedRequestIds.size() == 0);
|
|
|
|
CHECK(tester.m_expectedErrors == tester.m_receivedErrors);
|
|
|
|
// We should have received the deleted event
|
|
CHECK(tester.n_wasDeleted);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class ViewModelPropertySubscriptionListener
|
|
: public CommandQueue::ViewModelInstanceListener
|
|
{
|
|
public:
|
|
virtual void onViewModelInstanceError(const ViewModelInstanceHandle handle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(error.size());
|
|
++m_receivedErrors;
|
|
}
|
|
|
|
virtual void onViewModelDeleted(const ViewModelInstanceHandle handle,
|
|
uint64_t requestId) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(!n_wasDeleted);
|
|
n_wasDeleted = true;
|
|
}
|
|
|
|
virtual void onViewModelDataReceived(
|
|
const ViewModelInstanceHandle handle,
|
|
uint64_t requestId,
|
|
CommandQueue::ViewModelInstanceData data) override
|
|
{
|
|
// We only get one sub callback per value, so instead of a dequeue we
|
|
// use a map of names to values that we expect, it should always be the
|
|
// last value set.
|
|
CHECK(m_expectedData.size());
|
|
auto itr = m_expectedData.find(data.metaData.name);
|
|
CHECK(itr != m_expectedData.end());
|
|
auto& expectedData = itr->second;
|
|
|
|
CHECK(handle == m_handle);
|
|
CHECK(data == expectedData);
|
|
|
|
++m_receivedCallbacks;
|
|
}
|
|
|
|
void pushTriggerExpectation(CommandQueue* queue, std::string name)
|
|
{
|
|
++m_requestIdx;
|
|
m_expectedData[name] = {.metaData = {DataType::trigger, name}};
|
|
queue->fireViewModelTrigger(m_handle, name, m_requestIdx);
|
|
}
|
|
|
|
void pushExpectation(CommandQueue* queue, std::string name, float value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->setViewModelInstanceNumber(m_handle, name, value, m_requestIdx);
|
|
m_expectedData[name] = {.metaData = {DataType::number, name},
|
|
.numberValue = value};
|
|
}
|
|
|
|
void pushExpectation(CommandQueue* queue, std::string name, ColorInt value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->setViewModelInstanceColor(m_handle, name, value, m_requestIdx);
|
|
m_expectedData[name] = {.metaData = {DataType::color, name},
|
|
.colorValue = value};
|
|
}
|
|
|
|
void pushExpectation(CommandQueue* queue, std::string name, bool value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->setViewModelInstanceBool(m_handle, name, value, m_requestIdx);
|
|
m_expectedData[name] = {.metaData = {DataType::boolean, name},
|
|
.boolValue = value};
|
|
}
|
|
|
|
void pushExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
ViewModelInstanceHandle value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->setViewModelInstanceNestedViewModel(m_handle,
|
|
name,
|
|
value,
|
|
m_requestIdx);
|
|
// there is no subscription for view models, you must subscribe to the
|
|
// nested property instead
|
|
}
|
|
|
|
void pushListExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
ViewModelInstanceHandle value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->appendViewModelInstanceListViewModel(m_handle,
|
|
name,
|
|
value,
|
|
m_requestIdx);
|
|
// there is no value for view models list subscription callbacks
|
|
m_expectedData[name] = {.metaData = {DataType::viewModel, name}};
|
|
}
|
|
|
|
void pushStringExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
std::string value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->setViewModelInstanceString(m_handle, name, value, m_requestIdx);
|
|
m_expectedData[name] = {.metaData = {DataType::string, name},
|
|
.stringValue = value};
|
|
}
|
|
|
|
void pushEnumExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
std::string value)
|
|
{
|
|
++m_requestIdx;
|
|
queue->setViewModelInstanceEnum(m_handle, name, value, m_requestIdx);
|
|
m_expectedData[name] = {.metaData = {DataType::enumType, name},
|
|
.stringValue = value};
|
|
}
|
|
|
|
void pushExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
RenderImageHandle value)
|
|
{
|
|
queue->setViewModelInstanceImage(m_handle, "Test Image", value);
|
|
// no value for image subscriptions
|
|
m_expectedData[name] = {.metaData = {DataType::assetImage, name}};
|
|
}
|
|
|
|
void pushBadExpectation(CommandQueue* queue, std::string name, float value)
|
|
{
|
|
queue->setViewModelInstanceNumber(m_handle, name, value, m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
void pushBadExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
ColorInt value)
|
|
{
|
|
queue->setViewModelInstanceColor(m_handle, name, value, m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
void pushBadExpectation(CommandQueue* queue, std::string name, bool value)
|
|
{
|
|
queue->setViewModelInstanceBool(m_handle, name, value, m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
void pushBadExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
RenderImageHandle value)
|
|
{
|
|
queue->setViewModelInstanceImage(m_handle, name, value, m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
void pushBadExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
ViewModelInstanceHandle value)
|
|
{
|
|
queue->setViewModelInstanceNestedViewModel(m_handle,
|
|
name,
|
|
value,
|
|
m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
void pushBadStringExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
std::string value)
|
|
{
|
|
queue->setViewModelInstanceString(m_handle, name, value, m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
void pushBadEnumExpectation(CommandQueue* queue,
|
|
std::string name,
|
|
std::string value)
|
|
{
|
|
queue->setViewModelInstanceEnum(m_handle, name, value, m_requestIdx);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
void pushBadTriggerExpectation(CommandQueue* queue, std::string name)
|
|
{
|
|
queue->fireViewModelTrigger(m_handle, name);
|
|
++m_expectedErrors;
|
|
}
|
|
|
|
// One data and id per callback.
|
|
std::unordered_map<std::string, CommandQueue::ViewModelInstanceData>
|
|
m_expectedData;
|
|
// All of the handles should be the same.
|
|
ViewModelInstanceHandle m_handle;
|
|
bool n_wasDeleted = false;
|
|
|
|
uint64_t m_requestIdx = 1;
|
|
int m_receivedCallbacks = 0;
|
|
|
|
size_t m_expectedErrors = 0;
|
|
size_t m_receivedErrors = 0;
|
|
};
|
|
|
|
TEST_CASE("View Model Property Subscriptions", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::unique_ptr<gpu::RenderContext> nullContext =
|
|
RenderContextNULL::MakeContext();
|
|
// The subscriptions happens once at the end of processCommands, so it's not
|
|
// order dependent, that makes it more difficult to test. To get around
|
|
// this, we just make the server on the same thread. Different tests will be
|
|
// used for testing async.
|
|
CommandServer server(commandQueue, nullContext.get());
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
|
|
ViewModelPropertySubscriptionListener tester;
|
|
|
|
auto artboardHandle = commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
tester.m_handle =
|
|
commandQueue->instantiateDefaultViewModelInstance(fileHandle,
|
|
artboardHandle,
|
|
&tester);
|
|
|
|
commandQueue->subscribeToViewModelProperty(tester.m_handle,
|
|
"Test Nested/Nested Number",
|
|
DataType::number);
|
|
commandQueue->subscribeToViewModelProperty(tester.m_handle,
|
|
"Test Bool",
|
|
DataType::boolean);
|
|
commandQueue->subscribeToViewModelProperty(tester.m_handle,
|
|
"Test Num",
|
|
DataType::number);
|
|
commandQueue->subscribeToViewModelProperty(tester.m_handle,
|
|
"Test Color",
|
|
DataType::color);
|
|
commandQueue->subscribeToViewModelProperty(tester.m_handle,
|
|
"Test Enum",
|
|
DataType::enumType);
|
|
commandQueue->subscribeToViewModelProperty(tester.m_handle,
|
|
"Test String",
|
|
DataType::string);
|
|
commandQueue->subscribeToViewModelProperty(tester.m_handle,
|
|
"Test Trigger",
|
|
DataType::trigger);
|
|
commandQueue->subscribeToViewModelProperty(tester.m_handle,
|
|
"Test List",
|
|
DataType::list);
|
|
commandQueue->subscribeToViewModelProperty(tester.m_handle,
|
|
"Test Image",
|
|
DataType::assetImage);
|
|
|
|
commandQueue->subscribeToViewModelProperty(tester.m_handle,
|
|
"Bad property",
|
|
DataType::assetImage);
|
|
++tester.m_expectedErrors;
|
|
|
|
// bad type
|
|
commandQueue->subscribeToViewModelProperty(tester.m_handle,
|
|
"Test Image",
|
|
DataType::integer);
|
|
++tester.m_expectedErrors;
|
|
|
|
commandQueue->runOnce([](CommandServer* server) {
|
|
auto subs = server->testing_getSubsciptions();
|
|
CHECK(subs.size() == 9);
|
|
});
|
|
|
|
auto blankHandle =
|
|
commandQueue->instantiateBlankViewModelInstance(fileHandle,
|
|
"Nested VM");
|
|
auto alternateHandle =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
"Nested VM",
|
|
"Alternate Nested");
|
|
|
|
tester.pushExpectation(commandQueue.get(), "Test Bool", true);
|
|
tester.pushExpectation(commandQueue.get(), "Test Num", 10.0f);
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Nested/Nested Number",
|
|
10.0f);
|
|
tester.pushExpectation(commandQueue.get(), "Test Nested", blankHandle);
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Nested/Nested Number",
|
|
10.0f);
|
|
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Color",
|
|
rive::colorARGB(255, 255, 0, 0));
|
|
tester.pushEnumExpectation(commandQueue.get(), "Test Enum", "Value 2");
|
|
tester.pushStringExpectation(commandQueue.get(),
|
|
"Test String",
|
|
"Some String");
|
|
|
|
tester.pushTriggerExpectation(commandQueue.get(), "Test Trigger");
|
|
|
|
std::ifstream imageStream("assets/batdude.png", std::ios::binary);
|
|
auto imageHandle = commandQueue->decodeImage(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(imageStream), {}));
|
|
tester.pushExpectation(commandQueue.get(), "Test Image", imageHandle);
|
|
|
|
for (int i = 0; i < 10; ++i)
|
|
{
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Bool",
|
|
static_cast<bool>(i % 2));
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Num",
|
|
static_cast<float>(i));
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Nested",
|
|
i % 2 ? blankHandle : alternateHandle);
|
|
tester.pushExpectation(commandQueue.get(),
|
|
"Test Color",
|
|
rive::colorARGB(i, i, i, i));
|
|
tester.pushEnumExpectation(commandQueue.get(),
|
|
"Test Enum",
|
|
i % 2 ? "Value 2" : "Value 1");
|
|
tester.pushStringExpectation(commandQueue.get(),
|
|
"Test String",
|
|
std::to_string(i));
|
|
}
|
|
|
|
// Bad values
|
|
|
|
auto badImageHandle =
|
|
commandQueue->decodeImage(std::vector<uint8_t>(1024 * 1024, {}));
|
|
|
|
tester.pushBadExpectation(commandQueue.get(), "Test Image", badImageHandle);
|
|
|
|
commandQueue->deleteViewModelInstance(blankHandle);
|
|
commandQueue->deleteViewModelInstance(alternateHandle);
|
|
|
|
// Good property path bad value
|
|
tester.pushBadEnumExpectation(commandQueue.get(), "Test Enum", "Blah");
|
|
tester.pushBadExpectation(commandQueue.get(), "Test Nested", blankHandle);
|
|
|
|
// Bad everything
|
|
tester.pushBadExpectation(commandQueue.get(), "Blah", true);
|
|
tester.pushBadExpectation(commandQueue.get(), "Blah", 10.0f);
|
|
tester.pushBadExpectation(commandQueue.get(), "Blah", alternateHandle);
|
|
tester.pushBadExpectation(commandQueue.get(),
|
|
"Blah",
|
|
rive::colorARGB(255, 255, 0, 0));
|
|
tester.pushBadEnumExpectation(commandQueue.get(), "Blah", "Value 2");
|
|
tester.pushBadStringExpectation(commandQueue.get(), "Blah", "Some String");
|
|
|
|
tester.pushBadTriggerExpectation(commandQueue.get(), "Blah");
|
|
|
|
// Bad handle for trigger test.
|
|
commandQueue->fireViewModelTrigger(0, "Blah");
|
|
|
|
server.processCommands();
|
|
commandQueue->processMessages();
|
|
|
|
// Once the handle is delted subscriptions will stop, so we do this after
|
|
// processMessages.
|
|
commandQueue->deleteViewModelInstance(tester.m_handle);
|
|
|
|
// Call sets on deleted handle
|
|
tester.pushBadExpectation(commandQueue.get(), "Test Bool", true);
|
|
tester.pushBadExpectation(commandQueue.get(), "Test Num", 10.0f);
|
|
tester.pushBadExpectation(commandQueue.get(),
|
|
"Test Color",
|
|
rive::colorARGB(255, 255, 0, 0));
|
|
tester.pushBadEnumExpectation(commandQueue.get(), "Test Enum", "Value 2");
|
|
tester.pushBadStringExpectation(commandQueue.get(),
|
|
"Test String",
|
|
"Some String");
|
|
server.processCommands();
|
|
commandQueue->processMessages();
|
|
|
|
// We should have received the deleted event.
|
|
CHECK(tester.n_wasDeleted);
|
|
// We should have received the same number of callbacks as entries in the
|
|
// map.
|
|
CHECK(tester.m_receivedCallbacks == tester.m_expectedData.size());
|
|
|
|
commandQueue->unsubscribeToViewModelProperty(tester.m_handle,
|
|
"Test Nested/Nested Number",
|
|
DataType::number);
|
|
commandQueue->unsubscribeToViewModelProperty(tester.m_handle,
|
|
"Test Bool",
|
|
DataType::boolean);
|
|
commandQueue->unsubscribeToViewModelProperty(tester.m_handle,
|
|
"Test Num",
|
|
DataType::number);
|
|
commandQueue->unsubscribeToViewModelProperty(tester.m_handle,
|
|
"Test Color",
|
|
DataType::color);
|
|
commandQueue->unsubscribeToViewModelProperty(tester.m_handle,
|
|
"Test Enum",
|
|
DataType::enumType);
|
|
commandQueue->unsubscribeToViewModelProperty(tester.m_handle,
|
|
"Test String",
|
|
DataType::string);
|
|
commandQueue->unsubscribeToViewModelProperty(tester.m_handle,
|
|
"Test Trigger",
|
|
DataType::trigger);
|
|
commandQueue->unsubscribeToViewModelProperty(tester.m_handle,
|
|
"Test List",
|
|
DataType::list);
|
|
commandQueue->unsubscribeToViewModelProperty(tester.m_handle,
|
|
"Test Image",
|
|
DataType::assetImage);
|
|
// Unsub something that doesn't exist.
|
|
commandQueue->unsubscribeToViewModelProperty(tester.m_handle,
|
|
"Blah",
|
|
DataType::boolean);
|
|
// Note we don't increment tester.m_expectedErrors because unsubing
|
|
// something invalid is ok to do, we just ignore it.
|
|
|
|
commandQueue->runOnce([](CommandServer* server) {
|
|
auto subs = server->testing_getSubsciptions();
|
|
CHECK(subs.empty());
|
|
});
|
|
|
|
server.processCommands();
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(tester.m_receivedErrors == tester.m_expectedErrors);
|
|
|
|
commandQueue->disconnect();
|
|
}
|
|
|
|
class AsyncSubListener : public CommandQueue::ViewModelInstanceListener
|
|
{
|
|
public:
|
|
virtual void onViewModelDataReceived(
|
|
const ViewModelInstanceHandle handle,
|
|
uint64_t requestId,
|
|
CommandQueue::ViewModelInstanceData data) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(!m_hasCallback);
|
|
m_hasCallback = true;
|
|
// we are just expecting the one sub so the value should be correct
|
|
CHECK(data.numberValue == 10);
|
|
}
|
|
|
|
ViewModelInstanceHandle m_handle;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
// The above tests are to check that all subscription types work and that values
|
|
// come through correctly This is just checking to make sure subscriptions work
|
|
// while the server is in a seperate thread but we don't care about the exact
|
|
// callback happening since that is tested above
|
|
|
|
TEST_CASE("View Model Property Async Subscriptions", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
|
|
AsyncSubListener tester;
|
|
|
|
auto artboardHandle = commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
tester.m_handle =
|
|
commandQueue->instantiateDefaultViewModelInstance(fileHandle,
|
|
artboardHandle,
|
|
&tester);
|
|
|
|
commandQueue->subscribeToViewModelProperty(tester.m_handle,
|
|
"Test Num",
|
|
DataType::number);
|
|
|
|
commandQueue->setViewModelInstanceNumber(tester.m_handle, "Test Num", 10);
|
|
|
|
commandQueue->runOnce([](CommandServer* server) {
|
|
auto subs = server->testing_getSubsciptions();
|
|
CHECK(subs.size() == 1);
|
|
});
|
|
|
|
commandQueue->testing_commandLoopBreak();
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(tester.m_hasCallback);
|
|
|
|
commandQueue->unsubscribeToViewModelProperty(tester.m_handle,
|
|
"Test Num",
|
|
DataType::number);
|
|
|
|
commandQueue->runOnce([](CommandServer* server) {
|
|
auto subs = server->testing_getSubsciptions();
|
|
CHECK(subs.empty());
|
|
});
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class ListViewModelPropertyListener
|
|
: public CommandQueue::ViewModelInstanceListener
|
|
{
|
|
public:
|
|
ListViewModelPropertyListener(rcp<CommandQueue> queue) :
|
|
m_queue(std::move(queue))
|
|
{}
|
|
|
|
virtual void onViewModelListSizeReceived(
|
|
const ViewModelInstanceHandle handle,
|
|
uint64_t requestId,
|
|
std::string path,
|
|
size_t size) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(path == m_path);
|
|
CHECK(size == m_expectedSize);
|
|
CHECK(requestId == m_requestIdx);
|
|
m_receivedSize = true;
|
|
}
|
|
|
|
virtual void onViewModelDeleted(const ViewModelInstanceHandle handle,
|
|
uint64_t requestId) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(!n_wasDeleted);
|
|
n_wasDeleted = true;
|
|
}
|
|
|
|
void pushRequestExpectation(std::string path, size_t expectedSize)
|
|
{
|
|
m_path = path;
|
|
m_expectedSize = expectedSize;
|
|
++m_requestIdx;
|
|
m_receivedSize = false;
|
|
m_queue->requestViewModelInstanceListSize(m_handle,
|
|
m_path,
|
|
m_requestIdx);
|
|
|
|
wait_for_server(m_queue.get());
|
|
m_queue->processMessages();
|
|
CHECK(m_receivedSize);
|
|
}
|
|
|
|
void pushExpectation(std::string name,
|
|
ViewModelInstanceHandle expectedValueAtIndex1,
|
|
ViewModelInstanceHandle expectedValueAtIndex2,
|
|
int index,
|
|
int index2)
|
|
{
|
|
m_queue->swapViewModelInstanceListValues(m_handle, name, index, index2);
|
|
m_queue->runOnce([handle = m_handle,
|
|
name,
|
|
expectedValueAtIndex1,
|
|
expectedValueAtIndex2,
|
|
index,
|
|
index2](CommandServer* server) {
|
|
auto instance = server->getViewModelInstance(handle);
|
|
CHECK(instance != nullptr);
|
|
auto value1Instance =
|
|
server->getViewModelInstance(expectedValueAtIndex1);
|
|
CHECK(value1Instance != nullptr);
|
|
auto value2Instance =
|
|
server->getViewModelInstance(expectedValueAtIndex2);
|
|
CHECK(value2Instance != nullptr);
|
|
auto property = instance->propertyList(name);
|
|
CHECK(property != nullptr);
|
|
CHECK(property->instanceAt(index).get() == value1Instance);
|
|
CHECK(property->instanceAt(index2).get() == value2Instance);
|
|
});
|
|
}
|
|
|
|
void pushExpectation(std::string name,
|
|
ViewModelInstanceHandle value,
|
|
int index)
|
|
{
|
|
m_queue->insertViewModelInstanceListViewModel(m_handle,
|
|
name,
|
|
value,
|
|
index);
|
|
m_queue->runOnce(
|
|
[handle = m_handle, name, value, index](CommandServer* server) {
|
|
auto instance = server->getViewModelInstance(handle);
|
|
CHECK(instance != nullptr);
|
|
auto property = instance->propertyList(name);
|
|
CHECK(property != nullptr);
|
|
auto valueModel = server->getViewModelInstance(value);
|
|
CHECK(property->instanceAt(index).get() == valueModel);
|
|
});
|
|
}
|
|
|
|
void pushExpectation(std::string name, ViewModelInstanceHandle value)
|
|
{
|
|
m_queue->appendViewModelInstanceListViewModel(m_handle, name, value);
|
|
m_queue->runOnce([handle = m_handle, name, value](
|
|
CommandServer* server) {
|
|
auto instance = server->getViewModelInstance(handle);
|
|
CHECK(instance != nullptr);
|
|
auto property = instance->propertyList(name);
|
|
CHECK(property != nullptr);
|
|
auto valueModel = server->getViewModelInstance(value);
|
|
CHECK(property->instanceAt(static_cast<int>(property->size()) - 1)
|
|
.get() == valueModel);
|
|
});
|
|
}
|
|
|
|
void pushBadExpectation(std::string name,
|
|
ViewModelInstanceHandle value,
|
|
int index)
|
|
{
|
|
m_queue->insertViewModelInstanceListViewModel(m_handle,
|
|
name,
|
|
value,
|
|
index);
|
|
}
|
|
|
|
void pushBadExpectation(std::string name, ViewModelInstanceHandle value)
|
|
{
|
|
m_queue->appendViewModelInstanceListViewModel(m_handle, name, value);
|
|
}
|
|
|
|
void pushBadExpectation(std::string name, int index, int index2)
|
|
{
|
|
m_queue->swapViewModelInstanceListValues(m_handle, name, index, index2);
|
|
}
|
|
|
|
void pushBadRequestExpectation(std::string name)
|
|
{
|
|
m_receivedSize = false;
|
|
m_queue->requestViewModelInstanceListSize(m_handle, name);
|
|
|
|
wait_for_server(m_queue.get());
|
|
m_queue->processMessages();
|
|
CHECK(!m_receivedSize);
|
|
}
|
|
|
|
rcp<CommandQueue> m_queue;
|
|
ViewModelInstanceHandle m_handle;
|
|
bool n_wasDeleted = false;
|
|
// size callback test values
|
|
|
|
std::string m_path;
|
|
size_t m_expectedSize;
|
|
uint64_t m_requestIdx = 1;
|
|
bool m_receivedSize = false;
|
|
};
|
|
|
|
TEST_CASE("List View Model Property Set/Get", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
|
|
ListViewModelPropertyListener tester(commandQueue);
|
|
auto artboardHandle = commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
tester.m_handle =
|
|
commandQueue->instantiateDefaultViewModelInstance(fileHandle,
|
|
artboardHandle,
|
|
&tester);
|
|
auto blankHandle =
|
|
commandQueue->instantiateBlankViewModelInstance(fileHandle,
|
|
"Nested VM");
|
|
auto alternateHandle =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
"Nested VM",
|
|
"Alternate Nested");
|
|
tester.pushExpectation("Test List", blankHandle);
|
|
tester.pushExpectation("Test List", alternateHandle);
|
|
tester.pushExpectation("Test List", alternateHandle, blankHandle, 0, 1);
|
|
tester.pushRequestExpectation("Test List", 2);
|
|
tester.pushExpectation("Test List", blankHandle, 0);
|
|
tester.pushExpectation("Test List", alternateHandle, 0);
|
|
tester.pushExpectation("Test List", blankHandle, alternateHandle, 0, 1);
|
|
tester.pushRequestExpectation("Test List", 4);
|
|
|
|
auto badBlankHandle =
|
|
commandQueue->instantiateBlankViewModelInstance(fileHandle, "blah");
|
|
auto badAlternateHandle =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
"Nested VM",
|
|
"blah");
|
|
tester.pushBadExpectation("Test List", badBlankHandle);
|
|
tester.pushBadExpectation("Test List", badAlternateHandle);
|
|
tester.pushBadExpectation("Test List", badBlankHandle, 0);
|
|
tester.pushBadExpectation("Test List", badAlternateHandle, 0);
|
|
|
|
tester.pushBadExpectation("blah", blankHandle);
|
|
tester.pushBadExpectation("blah", alternateHandle);
|
|
tester.pushBadExpectation("blah", blankHandle, 0);
|
|
tester.pushBadExpectation("blah", alternateHandle, 0);
|
|
|
|
tester.pushBadExpectation("Test List", 10, 1);
|
|
tester.pushBadExpectation("Test List", 0, 10);
|
|
tester.pushBadExpectation("Blah", 0, 1);
|
|
tester.pushBadExpectation("Blah", 10, 1);
|
|
tester.pushBadExpectation("Blah", 0, 10);
|
|
|
|
tester.pushRequestExpectation("Test List", 4);
|
|
|
|
tester.pushBadRequestExpectation("Blah");
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class TestFileErrorListener : public CommandQueue::FileListener
|
|
{
|
|
public:
|
|
virtual void onFileError(const FileHandle handle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(error.size());
|
|
++m_receivedErrors;
|
|
}
|
|
|
|
FileHandle m_handle;
|
|
size_t m_receivedErrors = 0;
|
|
};
|
|
|
|
TEST_CASE("file Error Messages", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
TestFileErrorListener fileListener;
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
fileListener.m_handle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
&fileListener);
|
|
|
|
commandQueue->instantiateArtboardNamed(fileListener.m_handle, "Blah");
|
|
commandQueue->instantiateViewModelInstanceNamed(fileListener.m_handle,
|
|
"Test All",
|
|
"blah");
|
|
commandQueue->instantiateViewModelInstanceNamed(fileListener.m_handle,
|
|
"blah",
|
|
"blah");
|
|
commandQueue->instantiateViewModelInstanceNamed(fileListener.m_handle,
|
|
nullptr,
|
|
"blah");
|
|
|
|
commandQueue->instantiateDefaultViewModelInstance(fileListener.m_handle,
|
|
"Blah");
|
|
commandQueue->instantiateDefaultViewModelInstance(fileListener.m_handle,
|
|
nullptr);
|
|
|
|
commandQueue->instantiateBlankViewModelInstance(fileListener.m_handle,
|
|
"Blah");
|
|
commandQueue->instantiateBlankViewModelInstance(fileListener.m_handle,
|
|
nullptr);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(fileListener.m_receivedErrors == 8);
|
|
|
|
TestFileErrorListener badFileListener;
|
|
badFileListener.m_handle =
|
|
commandQueue->loadFile(std::vector<uint8_t>(100 * 1024, 0),
|
|
&badFileListener);
|
|
|
|
commandQueue->instantiateDefaultArtboard(badFileListener.m_handle);
|
|
commandQueue->instantiateDefaultViewModelInstance(badFileListener.m_handle,
|
|
nullptr);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
CHECK(badFileListener.m_receivedErrors == 3);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class TestFileListener : public CommandQueue::FileListener
|
|
{
|
|
public:
|
|
virtual void onArtboardsListed(
|
|
const FileHandle handle,
|
|
uint64_t requestId,
|
|
std::vector<std::string> artboardNames) override
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
CHECK(artboardNames.size() == m_artboardNames.size());
|
|
for (auto i = 0; i < artboardNames.size(); ++i)
|
|
{
|
|
CHECK(artboardNames[i] == m_artboardNames[i]);
|
|
}
|
|
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
uint64_t m_requestId;
|
|
FileHandle m_handle;
|
|
std::vector<std::string> m_artboardNames;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
TEST_CASE("listArtboard", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
TestFileListener fileListener;
|
|
|
|
std::ifstream stream("assets/entry.riv", std::ios::binary);
|
|
FileHandle goodFile = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
&fileListener);
|
|
|
|
fileListener.m_artboardNames = {"New Artboard", "New Artboard"};
|
|
fileListener.m_handle = goodFile;
|
|
|
|
fileListener.m_requestId = 0x40;
|
|
commandQueue->requestArtboardNames(goodFile, fileListener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(fileListener.m_hasCallback);
|
|
|
|
FileHandle badFile =
|
|
commandQueue->loadFile(std::vector<uint8_t>(100 * 1024, 0));
|
|
fileListener.m_handle = goodFile;
|
|
fileListener.m_hasCallback = false;
|
|
|
|
commandQueue->requestArtboardNames(badFile);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
CHECK(!fileListener.m_hasCallback);
|
|
|
|
commandQueue->processMessages();
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class TestFileEnumListener : public CommandQueue::FileListener
|
|
{
|
|
public:
|
|
virtual void onViewModelEnumsListed(const FileHandle handle,
|
|
uint64_t requestId,
|
|
std::vector<ViewModelEnum> enums)
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
CHECK(enums.size() == m_enums.size());
|
|
for (auto i = 0; i < enums.size(); ++i)
|
|
{
|
|
CHECK(enums[i].name == m_enums[i].name);
|
|
for (auto k = 0; k < enums[i].enumerants.size(); ++k)
|
|
{
|
|
CHECK(enums[i].enumerants[k] == m_enums[i].enumerants[k]);
|
|
}
|
|
}
|
|
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
uint64_t m_requestId;
|
|
FileHandle m_handle;
|
|
std::array<ViewModelEnum, 1> m_enums = {
|
|
ViewModelEnum{"Test Enum Values", {"Value 1", "Value 2"}}};
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
TEST_CASE("listEnums", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
TestFileEnumListener fileListener;
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
FileHandle goodFile = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
&fileListener);
|
|
|
|
fileListener.m_handle = goodFile;
|
|
|
|
fileListener.m_requestId = 0x40;
|
|
commandQueue->requestViewModelEnums(goodFile, fileListener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(fileListener.m_hasCallback);
|
|
|
|
FileHandle badFile =
|
|
commandQueue->loadFile(std::vector<uint8_t>(100 * 1024, 0));
|
|
fileListener.m_handle = goodFile;
|
|
fileListener.m_hasCallback = false;
|
|
|
|
commandQueue->requestViewModelEnums(badFile);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
CHECK(!fileListener.m_hasCallback);
|
|
|
|
commandQueue->processMessages();
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class TestRenderImageErrorListener : public CommandQueue::RenderImageListener
|
|
{
|
|
public:
|
|
virtual void onRenderImageError(const RenderImageHandle handle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(error.size());
|
|
CHECK(!m_hasCallback);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
RenderImageHandle m_handle;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
class TestAudioSourceErrorListener : public CommandQueue::AudioSourceListener
|
|
{
|
|
public:
|
|
virtual void onAudioSourceError(const AudioSourceHandle handle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(error.size());
|
|
CHECK(!m_hasCallback);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
AudioSourceHandle m_handle;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
class TestFontErrorListener : public CommandQueue::FontListener
|
|
{
|
|
public:
|
|
virtual void onFontError(const FontHandle handle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(error.size());
|
|
CHECK(!m_hasCallback);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
FontHandle m_handle;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
TEST_CASE("render image / audio source / font error", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
TestRenderImageErrorListener imageListener;
|
|
TestAudioSourceErrorListener audioListener;
|
|
TestFontErrorListener fontListener;
|
|
|
|
imageListener.m_handle =
|
|
commandQueue->decodeImage(std::vector<uint8_t>(1024, 0),
|
|
&imageListener);
|
|
audioListener.m_handle =
|
|
commandQueue->decodeAudio(std::vector<uint8_t>(1024, 0),
|
|
&audioListener);
|
|
fontListener.m_handle =
|
|
commandQueue->decodeFont(std::vector<uint8_t>(1024, 0), &fontListener);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(imageListener.m_hasCallback);
|
|
CHECK(audioListener.m_hasCallback);
|
|
CHECK(fontListener.m_hasCallback);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class TestStateMachineErrorListener : public CommandQueue::StateMachineListener
|
|
{
|
|
public:
|
|
virtual void onStateMachineError(const StateMachineHandle handle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(error.size());
|
|
++m_receivedErrors;
|
|
}
|
|
|
|
StateMachineHandle m_handle;
|
|
size_t m_receivedErrors = 0;
|
|
};
|
|
|
|
TEST_CASE("state machine error", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
TestStateMachineErrorListener listener;
|
|
|
|
std::ifstream stream("assets/entry.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
|
|
auto artboardHandle = commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
|
|
listener.m_handle =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle, &listener);
|
|
commandQueue->bindViewModelInstance(listener.m_handle, nullptr);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(listener.m_receivedErrors == 1);
|
|
|
|
TestStateMachineErrorListener badListener;
|
|
badListener.m_handle =
|
|
commandQueue->instantiateDefaultStateMachine(nullptr, &badListener);
|
|
|
|
commandQueue->advanceStateMachine(badListener.m_handle, 0);
|
|
commandQueue->pointerDown(badListener.m_handle, {});
|
|
commandQueue->pointerExit(badListener.m_handle, {});
|
|
commandQueue->pointerUp(badListener.m_handle, {});
|
|
commandQueue->pointerMove(badListener.m_handle, {});
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(badListener.m_receivedErrors == 5);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class TestArtboardErrorListener : public CommandQueue::ArtboardListener
|
|
{
|
|
public:
|
|
virtual void onArtboardError(const ArtboardHandle handle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{
|
|
CHECK(handle == m_handle);
|
|
CHECK(error.size());
|
|
++m_receivedErrors;
|
|
}
|
|
|
|
size_t m_receivedErrors = 0;
|
|
ArtboardHandle m_handle;
|
|
};
|
|
|
|
TEST_CASE("artboard errors", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/entry.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
|
|
TestArtboardErrorListener artboardListener;
|
|
|
|
artboardListener.m_handle =
|
|
commandQueue->instantiateArtboardNamed(fileHandle,
|
|
"New Artboard",
|
|
&artboardListener);
|
|
|
|
commandQueue->instantiateStateMachineNamed(artboardListener.m_handle,
|
|
"Blah");
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(artboardListener.m_receivedErrors == 1);
|
|
|
|
TestArtboardErrorListener badArtboardListener;
|
|
badArtboardListener.m_handle =
|
|
commandQueue->instantiateArtboardNamed(fileHandle,
|
|
"Blah",
|
|
&badArtboardListener);
|
|
|
|
commandQueue->requestStateMachineNames(badArtboardListener.m_handle);
|
|
commandQueue->instantiateDefaultStateMachine(badArtboardListener.m_handle);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(badArtboardListener.m_receivedErrors == 2);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class TestArtboardListener : public CommandQueue::ArtboardListener
|
|
{
|
|
public:
|
|
virtual void onStateMachinesListed(
|
|
const ArtboardHandle handle,
|
|
uint64_t requestId,
|
|
std::vector<std::string> stateMachineNames) override
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
CHECK(stateMachineNames.size() == m_stateMachineNames.size());
|
|
for (auto i = 0; i < stateMachineNames.size(); ++i)
|
|
{
|
|
CHECK(stateMachineNames[i] == m_stateMachineNames[i]);
|
|
}
|
|
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
uint64_t m_requestId;
|
|
ArtboardHandle m_handle;
|
|
std::vector<std::string> m_stateMachineNames;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
TEST_CASE("listStateMachine", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/entry.riv", std::ios::binary);
|
|
FileHandle goodFile = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
|
|
TestArtboardListener artboardListener;
|
|
|
|
auto artboardHandle =
|
|
commandQueue->instantiateArtboardNamed(goodFile,
|
|
"New Artboard",
|
|
&artboardListener);
|
|
artboardListener.m_stateMachineNames = {"State Machine 1"};
|
|
artboardListener.m_handle = artboardHandle;
|
|
|
|
artboardListener.m_requestId = 0x40;
|
|
commandQueue->requestStateMachineNames(artboardHandle,
|
|
artboardListener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(artboardListener.m_hasCallback);
|
|
|
|
FileHandle badFile =
|
|
commandQueue->loadFile(std::vector<uint8_t>(100 * 1024, 0));
|
|
|
|
auto badArtbaord = commandQueue->instantiateDefaultArtboard(badFile);
|
|
|
|
artboardListener.m_handle = badArtbaord;
|
|
artboardListener.m_hasCallback = false;
|
|
|
|
artboardListener.m_requestId = 0x40;
|
|
commandQueue->requestStateMachineNames(badArtbaord,
|
|
artboardListener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(!artboardListener.m_hasCallback);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class TestArtboardDefaultViewModelListener
|
|
: public CommandQueue::ArtboardListener
|
|
{
|
|
public:
|
|
virtual void onArtboardError(const ArtboardHandle handle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{
|
|
CHECK(m_hasErrorCallback == false);
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
m_hasErrorCallback = true;
|
|
}
|
|
|
|
virtual void onDefaultViewModelInfoReceived(
|
|
const ArtboardHandle handle,
|
|
uint64_t requestId,
|
|
std::string viewModelName,
|
|
std::string viewModelInstanceName) override
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
CHECK(viewModelName == m_expectedViewModel);
|
|
CHECK(viewModelInstanceName == m_expectedViewModelInstance);
|
|
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
uint64_t m_requestId;
|
|
ArtboardHandle m_handle;
|
|
std::string m_expectedViewModel = "Test All";
|
|
std::string m_expectedViewModelInstance = "Test Default";
|
|
bool m_hasCallback = false;
|
|
bool m_hasErrorCallback = false;
|
|
};
|
|
|
|
TEST_CASE("requestDefaultViewModel", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
FileHandle goodFile = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
|
|
TestArtboardDefaultViewModelListener artboardListener;
|
|
|
|
auto artboardHandle =
|
|
commandQueue->instantiateArtboardNamed(goodFile,
|
|
"Test Artboard",
|
|
&artboardListener);
|
|
artboardListener.m_handle = artboardHandle;
|
|
|
|
artboardListener.m_requestId = 0x40;
|
|
commandQueue->requestDefaultViewModelInfo(artboardHandle,
|
|
goodFile,
|
|
artboardListener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(artboardListener.m_hasCallback);
|
|
CHECK(!artboardListener.m_hasErrorCallback);
|
|
|
|
FileHandle badFile =
|
|
commandQueue->loadFile(std::vector<uint8_t>(100 * 1024, 0));
|
|
|
|
auto badArtbaord = commandQueue->instantiateDefaultArtboard(badFile);
|
|
|
|
artboardListener.m_hasCallback = false;
|
|
|
|
commandQueue->requestDefaultViewModelInfo(badArtbaord,
|
|
goodFile,
|
|
artboardListener.m_requestId);
|
|
commandQueue->requestDefaultViewModelInfo(artboardHandle,
|
|
badFile,
|
|
artboardListener.m_requestId);
|
|
commandQueue->requestDefaultViewModelInfo(badArtbaord,
|
|
badFile,
|
|
artboardListener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(!artboardListener.m_hasCallback);
|
|
CHECK(artboardListener.m_hasErrorCallback);
|
|
|
|
std::ifstream tstream("assets/entry.riv", std::ios::binary);
|
|
FileHandle noViewModelFile = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(tstream), {}));
|
|
|
|
TestArtboardDefaultViewModelListener noViewModelListener;
|
|
noViewModelListener.m_handle =
|
|
commandQueue->instantiateDefaultArtboard(noViewModelFile,
|
|
&noViewModelListener);
|
|
|
|
commandQueue->requestDefaultViewModelInfo(noViewModelListener.m_handle,
|
|
noViewModelFile,
|
|
noViewModelListener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(artboardListener.m_hasErrorCallback);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class TestStateMachineListener : public CommandQueue::StateMachineListener
|
|
{
|
|
public:
|
|
virtual void onStateMachineDeleted(const StateMachineHandle handle,
|
|
uint64_t requestId)
|
|
{
|
|
CHECK(m_handle == handle);
|
|
}
|
|
|
|
virtual void onStateMachineSettled(const StateMachineHandle handle,
|
|
uint64_t requestId)
|
|
{
|
|
CHECK(m_handle == handle);
|
|
CHECK(m_requestId == requestId);
|
|
m_hasCallbck = true;
|
|
}
|
|
|
|
StateMachineHandle m_handle = RIVE_NULL_HANDLE;
|
|
uint64_t m_requestId = 0;
|
|
bool m_hasCallbck = false;
|
|
};
|
|
|
|
TEST_CASE("bindViewModelInstance", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::unique_ptr<gpu::RenderContext> nullContext =
|
|
RenderContextNULL::MakeContext();
|
|
CommandServer server(commandQueue, nullContext.get());
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
|
|
auto viewModelHandle =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
"Test All",
|
|
"Test Alternate");
|
|
|
|
auto artboard = commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
auto stateMachineHandle =
|
|
commandQueue->instantiateDefaultStateMachine(artboard);
|
|
|
|
commandQueue->bindViewModelInstance(stateMachineHandle, viewModelHandle);
|
|
|
|
commandQueue->runOnce([stateMachineHandle,
|
|
viewModelHandle](CommandServer* server) {
|
|
auto stateMachine = server->getStateMachineInstance(stateMachineHandle);
|
|
CHECK(stateMachine != nullptr);
|
|
auto viewModel = server->getViewModelInstance(viewModelHandle);
|
|
CHECK(viewModel != nullptr);
|
|
|
|
CHECK(stateMachine->artboard()->dataContext()->viewModelInstance() ==
|
|
viewModel->instance());
|
|
});
|
|
|
|
auto badInstanceHandle =
|
|
commandQueue->instantiateViewModelInstanceNamed(fileHandle,
|
|
"blah",
|
|
"Test Alternate");
|
|
|
|
auto badStateMachineHandle =
|
|
commandQueue->instantiateStateMachineNamed(artboard, "blah");
|
|
|
|
// every combo of good / bad handles
|
|
|
|
commandQueue->bindViewModelInstance(stateMachineHandle, badInstanceHandle);
|
|
commandQueue->bindViewModelInstance(badStateMachineHandle, viewModelHandle);
|
|
commandQueue->bindViewModelInstance(badStateMachineHandle,
|
|
badInstanceHandle);
|
|
|
|
server.processCommands();
|
|
commandQueue->processMessages();
|
|
|
|
commandQueue->disconnect();
|
|
}
|
|
|
|
TEST_CASE("advanceStateMachine", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::unique_ptr<gpu::RenderContext> nullContext =
|
|
RenderContextNULL::MakeContext();
|
|
CommandServer server(commandQueue, nullContext.get());
|
|
|
|
std::ifstream stream("assets/settler.riv", std::ios::binary);
|
|
FileHandle goodFile = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
|
|
auto artboardHandle = commandQueue->instantiateDefaultArtboard(goodFile);
|
|
|
|
TestStateMachineListener stateMachineListener;
|
|
stateMachineListener.m_handle =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle,
|
|
&stateMachineListener);
|
|
|
|
commandQueue->advanceStateMachine(stateMachineListener.m_handle, 10);
|
|
commandQueue->advanceStateMachine(stateMachineListener.m_handle,
|
|
10.0,
|
|
stateMachineListener.m_requestId);
|
|
// the last advance is what actually settles the statemachine, so we store
|
|
// that id.
|
|
|
|
stateMachineListener.m_requestId = 0x50;
|
|
commandQueue->advanceStateMachine(stateMachineListener.m_handle,
|
|
10.0,
|
|
stateMachineListener.m_requestId);
|
|
|
|
server.processCommands();
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(stateMachineListener.m_hasCallbck);
|
|
|
|
TestStateMachineListener badStateMachineListener;
|
|
|
|
badStateMachineListener.m_handle =
|
|
commandQueue->instantiateStateMachineNamed(artboardHandle,
|
|
"blah blah",
|
|
&badStateMachineListener);
|
|
badStateMachineListener.m_requestId = 0x51;
|
|
commandQueue->advanceStateMachine(badStateMachineListener.m_handle,
|
|
10,
|
|
badStateMachineListener.m_requestId);
|
|
|
|
server.processCommands();
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(!badStateMachineListener.m_hasCallbck);
|
|
}
|
|
|
|
class DeleteFileListener : public CommandQueue::FileListener
|
|
{
|
|
public:
|
|
virtual void onFileDeleted(const FileHandle handle,
|
|
uint64_t requestId) override
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
uint64_t m_requestId;
|
|
FileHandle m_handle;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
class DeleteArtboardListener : public CommandQueue::ArtboardListener
|
|
{
|
|
public:
|
|
virtual void onArtboardDeleted(const ArtboardHandle handle,
|
|
uint64_t requestId) override
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
uint64_t m_requestId;
|
|
ArtboardHandle m_handle;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
class DeleteStateMachineListener : public CommandQueue::StateMachineListener
|
|
{
|
|
public:
|
|
virtual void onStateMachineDeleted(const StateMachineHandle handle,
|
|
uint64_t requestId) override
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
uint64_t m_requestId;
|
|
StateMachineHandle m_handle;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
class DeleteRenderImageListener : public CommandQueue::RenderImageListener
|
|
{
|
|
public:
|
|
virtual void onRenderImageDeleted(const RenderImageHandle handle,
|
|
uint64_t requestId) override
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
uint64_t m_requestId;
|
|
RenderImageHandle m_handle;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
TEST_CASE("listenerDeleteCallbacks", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
DeleteFileListener fileListener;
|
|
DeleteArtboardListener artboardListener;
|
|
DeleteStateMachineListener stateMachineListener;
|
|
DeleteRenderImageListener renderImageListener;
|
|
|
|
std::ifstream stream("assets/entry.riv", std::ios::binary);
|
|
FileHandle goodFile = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
&fileListener);
|
|
|
|
auto artboardHandle =
|
|
commandQueue->instantiateArtboardNamed(goodFile,
|
|
"New Artboard",
|
|
&artboardListener);
|
|
|
|
auto stateMachineHandle =
|
|
commandQueue->instantiateStateMachineNamed(artboardHandle,
|
|
"",
|
|
&stateMachineListener);
|
|
|
|
std::ifstream imageStream("assets/batdude.png", std::ios::binary);
|
|
auto renderImage = commandQueue->decodeImage(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
&renderImageListener);
|
|
|
|
fileListener.m_handle = goodFile;
|
|
artboardListener.m_handle = artboardHandle;
|
|
stateMachineListener.m_handle = stateMachineHandle;
|
|
renderImageListener.m_handle = renderImage;
|
|
|
|
CHECK(!fileListener.m_hasCallback);
|
|
CHECK(!artboardListener.m_hasCallback);
|
|
CHECK(!stateMachineListener.m_hasCallback);
|
|
CHECK(!renderImageListener.m_hasCallback);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(!fileListener.m_hasCallback);
|
|
CHECK(!artboardListener.m_hasCallback);
|
|
CHECK(!stateMachineListener.m_hasCallback);
|
|
CHECK(!renderImageListener.m_hasCallback);
|
|
|
|
stateMachineListener.m_requestId = 0x50;
|
|
commandQueue->deleteStateMachine(stateMachineHandle,
|
|
stateMachineListener.m_requestId);
|
|
artboardListener.m_requestId = 0x51,
|
|
commandQueue->deleteArtboard(artboardHandle, artboardListener.m_requestId);
|
|
fileListener.m_requestId = 0x52;
|
|
commandQueue->deleteFile(goodFile, fileListener.m_requestId);
|
|
renderImageListener.m_requestId = 0x53;
|
|
commandQueue->deleteImage(renderImage, renderImageListener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(fileListener.m_hasCallback);
|
|
CHECK(artboardListener.m_hasCallback);
|
|
CHECK(stateMachineListener.m_hasCallback);
|
|
CHECK(renderImageListener.m_hasCallback);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class LoadedFileListener : public CommandQueue::FileListener
|
|
{
|
|
public:
|
|
virtual void onFileLoaded(const FileHandle handle,
|
|
uint64_t requestId) override
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
uint64_t m_requestId = 0x10;
|
|
FileHandle m_handle = RIVE_NULL_HANDLE;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
TEST_CASE("fileLoadedCallback", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
std::ifstream stream("assets/entry.riv", std::ios::binary);
|
|
|
|
LoadedFileListener fileListener;
|
|
|
|
fileListener.m_handle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
&fileListener,
|
|
fileListener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(fileListener.m_hasCallback);
|
|
|
|
LoadedFileListener badFileListener;
|
|
badFileListener.m_handle =
|
|
commandQueue->loadFile(std::vector<uint8_t>(1024, {}),
|
|
&badFileListener,
|
|
badFileListener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(!badFileListener.m_hasCallback);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
class DecodedImageListener : public CommandQueue::RenderImageListener
|
|
{
|
|
public:
|
|
virtual void onRenderImageDecoded(const RenderImageHandle handle,
|
|
uint64_t requestId) override
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
uint64_t m_requestId = 0x10;
|
|
RenderImageHandle m_handle = RIVE_NULL_HANDLE;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
class DecodedAudioListener : public CommandQueue::AudioSourceListener
|
|
{
|
|
public:
|
|
virtual void onAudioSourceDecoded(const AudioSourceHandle handle,
|
|
uint64_t requestId) override
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
uint64_t m_requestId = 0x10;
|
|
AudioSourceHandle m_handle = RIVE_NULL_HANDLE;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
class DecodedFontListener : public CommandQueue::FontListener
|
|
{
|
|
public:
|
|
virtual void onFontDecoded(const FontHandle handle,
|
|
uint64_t requestId) override
|
|
{
|
|
CHECK(requestId == m_requestId);
|
|
CHECK(handle == m_handle);
|
|
m_hasCallback = true;
|
|
}
|
|
|
|
uint64_t m_requestId = 0x10;
|
|
FontHandle m_handle = RIVE_NULL_HANDLE;
|
|
bool m_hasCallback = false;
|
|
};
|
|
|
|
TEST_CASE("decodedCallbacks", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
std::ifstream stream("assets/entry.riv", std::ios::binary);
|
|
|
|
DecodedImageListener imageListener;
|
|
DecodedAudioListener audioListener;
|
|
DecodedFontListener fontListener;
|
|
|
|
std::ifstream imageStream("assets/batdude.png", std::ios::binary);
|
|
imageListener.m_handle = commandQueue->decodeImage(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(imageStream), {}),
|
|
&imageListener,
|
|
imageListener.m_requestId);
|
|
|
|
std::ifstream audioStream("assets/audio/what.wav", std::ios::binary);
|
|
audioListener.m_handle = commandQueue->decodeAudio(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(audioStream), {}),
|
|
&audioListener,
|
|
audioListener.m_requestId);
|
|
|
|
std::ifstream fontStream("assets/fonts/OpenSans-Italic.ttf",
|
|
std::ios::binary);
|
|
fontListener.m_handle = commandQueue->decodeFont(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(fontStream), {}),
|
|
&fontListener,
|
|
fontListener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(imageListener.m_hasCallback);
|
|
CHECK(audioListener.m_hasCallback);
|
|
CHECK(fontListener.m_hasCallback);
|
|
|
|
DecodedImageListener badimageListener;
|
|
DecodedAudioListener badaudioListener;
|
|
DecodedFontListener badfontListener;
|
|
|
|
badimageListener.m_handle =
|
|
commandQueue->decodeImage(std::vector<uint8_t>(1024, {}),
|
|
&badimageListener,
|
|
badimageListener.m_requestId);
|
|
|
|
badaudioListener.m_handle =
|
|
commandQueue->decodeAudio(std::vector<uint8_t>(1024, {}),
|
|
&badaudioListener,
|
|
badaudioListener.m_requestId);
|
|
|
|
badfontListener.m_handle =
|
|
commandQueue->decodeFont(std::vector<uint8_t>(1024, {}),
|
|
&badfontListener,
|
|
badfontListener.m_requestId);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(!badimageListener.m_hasCallback);
|
|
CHECK(!badaudioListener.m_hasCallback);
|
|
CHECK(!badfontListener.m_hasCallback);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
TEST_CASE("listenerLifeTimes", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/entry.riv", std::ios::binary);
|
|
|
|
CommandQueue::FileListener fileListener;
|
|
CommandQueue::ArtboardListener artboardListener;
|
|
CommandQueue::StateMachineListener stateMachineListener;
|
|
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
&fileListener);
|
|
|
|
auto artboardHandle =
|
|
commandQueue->instantiateArtboardNamed(fileHandle,
|
|
"New Artboard",
|
|
&artboardListener);
|
|
|
|
auto stateMachineHandle =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle,
|
|
&stateMachineListener);
|
|
|
|
commandQueue->requestArtboardNames(fileHandle);
|
|
commandQueue->requestStateMachineNames(artboardHandle);
|
|
|
|
CHECK(commandQueue->testing_getFileListener(fileHandle));
|
|
CHECK(commandQueue->testing_getArtboardListener(artboardHandle));
|
|
CHECK(commandQueue->testing_getStateMachineListener(stateMachineHandle));
|
|
|
|
wait_for_server(commandQueue.get());
|
|
|
|
commandQueue->processMessages();
|
|
|
|
CommandQueue::FileListener deleteFileListener;
|
|
CommandQueue::ArtboardListener deleteArtboardListener;
|
|
CommandQueue::StateMachineListener deleteStateMachineListener;
|
|
|
|
FileHandle dFileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
&deleteFileListener);
|
|
|
|
auto dArtboardHandle =
|
|
commandQueue->instantiateArtboardNamed(fileHandle,
|
|
"New Artboard",
|
|
&deleteArtboardListener);
|
|
|
|
auto dStateMachineHandle = commandQueue->instantiateDefaultStateMachine(
|
|
artboardHandle,
|
|
&deleteStateMachineListener);
|
|
|
|
CHECK(commandQueue->testing_getFileListener(dFileHandle));
|
|
CHECK(commandQueue->testing_getArtboardListener(dArtboardHandle));
|
|
CHECK(commandQueue->testing_getStateMachineListener(dStateMachineHandle));
|
|
|
|
commandQueue->deleteFile(dFileHandle);
|
|
commandQueue->deleteArtboard(dArtboardHandle);
|
|
commandQueue->deleteStateMachine(dStateMachineHandle);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK(commandQueue->testing_getFileListener(dFileHandle));
|
|
CHECK(commandQueue->testing_getArtboardListener(dArtboardHandle));
|
|
CHECK(commandQueue->testing_getStateMachineListener(dStateMachineHandle));
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
|
|
FileHandle fh;
|
|
|
|
{
|
|
CommandQueue::FileListener listener;
|
|
fh = commandQueue->loadFile(std::vector<uint8_t>(100 * 1024, 0),
|
|
&listener);
|
|
CHECK(commandQueue->testing_getFileListener(fh));
|
|
}
|
|
|
|
CHECK(!commandQueue->testing_getFileListener(fh));
|
|
|
|
ArtboardHandle ah;
|
|
{
|
|
CommandQueue::ArtboardListener listener;
|
|
ah = commandQueue->instantiateDefaultArtboard(fh, &listener);
|
|
CHECK(commandQueue->testing_getArtboardListener(ah));
|
|
}
|
|
CHECK(!commandQueue->testing_getArtboardListener(ah));
|
|
|
|
StateMachineHandle sh;
|
|
{
|
|
CommandQueue::StateMachineListener listener;
|
|
sh = commandQueue->instantiateDefaultStateMachine(ah, &listener);
|
|
CHECK(commandQueue->testing_getStateMachineListener(sh));
|
|
}
|
|
CHECK(!commandQueue->testing_getStateMachineListener(sh));
|
|
|
|
CHECK(commandQueue->testing_getFileListener(fileHandle));
|
|
CHECK(commandQueue->testing_getArtboardListener(artboardHandle));
|
|
CHECK(commandQueue->testing_getStateMachineListener(stateMachineHandle));
|
|
|
|
// If we move we should now point to the new listener
|
|
CommandQueue::FileListener newFileListener = std::move(fileListener);
|
|
auto listenerPtr = commandQueue->testing_getFileListener(fileHandle);
|
|
CHECK(&newFileListener == listenerPtr);
|
|
|
|
CommandQueue::ArtboardListener newArtboardListener =
|
|
std::move(artboardListener);
|
|
auto listenerPtr1 =
|
|
commandQueue->testing_getArtboardListener(artboardHandle);
|
|
CHECK(&newArtboardListener == listenerPtr1);
|
|
|
|
CommandQueue::StateMachineListener newStateMachineListener =
|
|
std::move(stateMachineListener);
|
|
auto listenerPtr2 =
|
|
commandQueue->testing_getStateMachineListener(stateMachineHandle);
|
|
CHECK(&newStateMachineListener == listenerPtr2);
|
|
|
|
// force unref the commandQueue to ensure it stays alive for listeners
|
|
commandQueue.reset();
|
|
|
|
// after this we are checking the destructors for fileListener,
|
|
// artboardListener and stateMachineListener as they should gracefully
|
|
// remove themselves from the commandQeueue even though the ref here is gone
|
|
}
|
|
|
|
TEST_CASE("empty test for code cove", "[CommandQueue]")
|
|
{
|
|
CommandQueue::FileListener fileL;
|
|
CommandQueue::ArtboardListener artboardL;
|
|
CommandQueue::StateMachineListener statemachineL;
|
|
|
|
std::vector<std::string> emptyVector;
|
|
|
|
fileL.onFileDeleted(0, 0);
|
|
fileL.onArtboardsListed(0, 0, emptyVector);
|
|
|
|
artboardL.onArtboardDeleted(0, 0);
|
|
artboardL.onStateMachinesListed(0, 0, emptyVector);
|
|
|
|
statemachineL.onStateMachineDeleted(0, 0);
|
|
statemachineL.onStateMachineSettled(0, 0);
|
|
|
|
CHECK(true);
|
|
}
|
|
|
|
// Helps with repetition of checks in the following test
|
|
void checkStateMachineBool(rcp<CommandQueue>& commandQueue,
|
|
StateMachineHandle handle,
|
|
const char* boolName,
|
|
bool expectedValue)
|
|
{
|
|
commandQueue->runOnce(
|
|
[handle, boolName, expectedValue](CommandServer* server) {
|
|
rive::StateMachineInstance* sm =
|
|
server->getStateMachineInstance(handle);
|
|
CHECK(sm->getBool(boolName)->value() == expectedValue);
|
|
});
|
|
}
|
|
|
|
TEST_CASE("pointer input", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/pointer_events.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
ArtboardHandle artboardHandle =
|
|
commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
StateMachineHandle smHandle =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle);
|
|
|
|
commandQueue->runOnce(
|
|
[fileHandle, artboardHandle, smHandle](CommandServer* server) {
|
|
CHECK(fileHandle != RIVE_NULL_HANDLE);
|
|
CHECK(server->getFile(fileHandle) != nullptr);
|
|
CHECK(artboardHandle != RIVE_NULL_HANDLE);
|
|
CHECK(server->getArtboardInstance(artboardHandle) != nullptr);
|
|
CHECK(smHandle != RIVE_NULL_HANDLE);
|
|
CHECK(server->getStateMachineInstance(smHandle) != nullptr);
|
|
});
|
|
|
|
// Prime for events by advancing once
|
|
commandQueue->advanceStateMachine(smHandle, 0.0f);
|
|
|
|
// The listener object in the bottom-left corner,
|
|
// which toggles `isDown` when receiving down events.
|
|
Vec2D toggleOnDownCorner(425.0f, 425.0f);
|
|
commandQueue->pointerDown(smHandle, {.position = toggleOnDownCorner});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", true);
|
|
commandQueue->pointerUp(smHandle, {.position = toggleOnDownCorner});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", true);
|
|
commandQueue->pointerDown(smHandle, {.position = toggleOnDownCorner});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", false);
|
|
|
|
// The listener object in the top-left corner,
|
|
// which toggles `isDown` when receiving down or up events.
|
|
Vec2D toggleOnDownOrUpCorner(75.0f, 75.0f);
|
|
commandQueue->pointerDown(smHandle, {.position = toggleOnDownOrUpCorner});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", true);
|
|
commandQueue->pointerUp(smHandle, {.position = toggleOnDownOrUpCorner});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", false);
|
|
|
|
// The listener object in the top-right corner,
|
|
// which toggles `isDown` when hovered.
|
|
Vec2D toggleOnHoverCorner(425.0f, 75.0f);
|
|
// Center, which has no pointer listener.
|
|
Vec2D center(250.0f, 250.0f);
|
|
commandQueue->pointerMove(smHandle, {.position = center});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", false);
|
|
commandQueue->pointerMove(smHandle, {.position = toggleOnHoverCorner});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", true);
|
|
commandQueue->pointerMove(smHandle, {.position = center});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", true);
|
|
commandQueue->pointerMove(smHandle, {.position = toggleOnHoverCorner});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", false);
|
|
|
|
// A position off of the artboard, which can be used for pointer exits.
|
|
Vec2D offArtboard(-25.0f, -25.0f);
|
|
commandQueue->pointerDown(smHandle, {.position = toggleOnDownOrUpCorner});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", true);
|
|
// Slide off while "holding down" the pointer.
|
|
commandQueue->pointerExit(smHandle, {.position = offArtboard});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", true);
|
|
// Release the pointer while off the artboard - should not toggle
|
|
commandQueue->pointerUp(smHandle, {.position = offArtboard});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", true);
|
|
// Reset
|
|
commandQueue->pointerUp(smHandle, {.position = toggleOnDownOrUpCorner});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", false);
|
|
|
|
// New test, sliding off and back, but this time releasing back on the
|
|
// artboard.
|
|
commandQueue->pointerDown(smHandle, {.position = toggleOnDownOrUpCorner});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", true);
|
|
commandQueue->pointerExit(smHandle, {.position = offArtboard});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", true);
|
|
commandQueue->pointerMove(smHandle, {.position = toggleOnDownOrUpCorner});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", true);
|
|
commandQueue->pointerUp(smHandle, {.position = toggleOnDownOrUpCorner});
|
|
checkStateMachineBool(commandQueue, smHandle, "isDown", false);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
static bool aboutEquals(const Vec2D& l, const Vec2D& r, float theta = 0.0001)
|
|
{
|
|
auto b = l - r;
|
|
return abs(b.x) < theta && abs(b.y) < theta;
|
|
}
|
|
|
|
TEST_CASE("pointer input translation", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::unique_ptr<gpu::RenderContext> nullContext =
|
|
RenderContextNULL::MakeContext();
|
|
CommandServer server(commandQueue, nullContext.get());
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}));
|
|
ArtboardHandle artboardHandle =
|
|
commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
StateMachineHandle smHandle =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle);
|
|
// artboard is 500x500 so 50x50 should translate to 250,250
|
|
|
|
// expected result
|
|
Vec2D expected(250, 250);
|
|
// Inputs
|
|
Vec2D center(50, 50);
|
|
Vec2D size(100, 100);
|
|
|
|
commandQueue->runOnce([expected, center, size, smHandle](
|
|
CommandServer* server) {
|
|
auto instance = server->getStateMachineInstance(smHandle);
|
|
CHECK(instance != nullptr);
|
|
auto translated = server->testing_cursorPosForPointerEvent(
|
|
instance,
|
|
{.fit = Fit::contain, .screenBounds = size, .position = center});
|
|
CHECK(aboutEquals(translated, expected));
|
|
});
|
|
|
|
// expected result
|
|
Vec2D expectedTL(125, 125);
|
|
// Inputs
|
|
Vec2D topLeft(25, 25);
|
|
|
|
commandQueue->runOnce([expectedTL, topLeft, size, smHandle](
|
|
CommandServer* server) {
|
|
auto instance = server->getStateMachineInstance(smHandle);
|
|
CHECK(instance != nullptr);
|
|
auto translated = server->testing_cursorPosForPointerEvent(
|
|
instance,
|
|
{.fit = Fit::contain, .screenBounds = size, .position = topLeft});
|
|
CHECK(aboutEquals(translated, expectedTL));
|
|
});
|
|
|
|
// expected result
|
|
Vec2D expectedTR(375, 375);
|
|
// Inputs
|
|
Vec2D topRight(75, 75);
|
|
|
|
commandQueue->runOnce([expectedTR, topRight, size, smHandle](
|
|
CommandServer* server) {
|
|
auto instance = server->getStateMachineInstance(smHandle);
|
|
CHECK(instance != nullptr);
|
|
auto translated = server->testing_cursorPosForPointerEvent(
|
|
instance,
|
|
{.fit = Fit::contain, .screenBounds = size, .position = topRight});
|
|
CHECK(aboutEquals(translated, expectedTR));
|
|
});
|
|
|
|
// expected result
|
|
Vec2D expectedBR(375, 125);
|
|
// Inputs
|
|
Vec2D bottomRight(75, 25);
|
|
|
|
commandQueue->runOnce([bottomRight, expectedBR, size, smHandle](
|
|
CommandServer* server) {
|
|
auto instance = server->getStateMachineInstance(smHandle);
|
|
CHECK(instance != nullptr);
|
|
auto translated =
|
|
server->testing_cursorPosForPointerEvent(instance,
|
|
{.fit = Fit::contain,
|
|
.screenBounds = size,
|
|
.position = bottomRight});
|
|
CHECK(aboutEquals(translated, expectedBR));
|
|
});
|
|
|
|
// expected result
|
|
Vec2D expectedBL(125, 375);
|
|
// Inputs
|
|
Vec2D bottomLeft(25, 75);
|
|
|
|
commandQueue->runOnce([bottomLeft, expectedBL, size, smHandle](
|
|
CommandServer* server) {
|
|
auto instance = server->getStateMachineInstance(smHandle);
|
|
CHECK(instance != nullptr);
|
|
auto translated =
|
|
server->testing_cursorPosForPointerEvent(instance,
|
|
{.fit = Fit::contain,
|
|
.screenBounds = size,
|
|
.position = bottomLeft});
|
|
CHECK(aboutEquals(translated, expectedBL));
|
|
});
|
|
|
|
server.processCommands();
|
|
commandQueue->processMessages();
|
|
|
|
commandQueue->disconnect();
|
|
}
|
|
|
|
// clang format is removing the needed space between the func names and the (
|
|
// clang-format off
|
|
#define DEFINE_TEST_CALLBACK(fun, handleType, expectedRequestId) \
|
|
bool m_##fun##WasCalled = false; \
|
|
virtual void fun (const handleType handle, uint64_t requestId) override \
|
|
{ \
|
|
CHECK(handle == m_handle); \
|
|
CHECK(requestId == expectedRequestId); \
|
|
m_##fun##WasCalled = true; \
|
|
}
|
|
|
|
#define DEFINE_TEST_CALLBACK_ONE_PARAM(fun, \
|
|
handleType, \
|
|
expectedRequestId, \
|
|
paramType, \
|
|
param) \
|
|
bool m_##fun##WasCalled = false; \
|
|
virtual void fun (const handleType handle, \
|
|
uint64_t requestId, \
|
|
paramType param) override \
|
|
{ \
|
|
CHECK(handle == m_handle); \
|
|
CHECK(requestId == expectedRequestId); \
|
|
CHECK(param == m_##param); \
|
|
m_##fun##WasCalled = true; \
|
|
}
|
|
|
|
#define DEFINE_TEST_CALLBACK_TWO_PARAM(fun, \
|
|
handleType, \
|
|
expectedRequestId, \
|
|
paramType, \
|
|
param, \
|
|
param2Type, \
|
|
param2) \
|
|
bool m_##fun##WasCalled = false; \
|
|
virtual void fun (const handleType handle, \
|
|
uint64_t requestId, \
|
|
paramType param, \
|
|
param2Type param2) override \
|
|
{ \
|
|
CHECK(handle == m_handle); \
|
|
CHECK(requestId == expectedRequestId); \
|
|
CHECK(param == m_##param); \
|
|
CHECK(param2 == m_##param2); \
|
|
m_##fun##WasCalled = true; \
|
|
}
|
|
// clang-format on
|
|
#define CHECK_CALLBACK(obj, func) CHECK(obj.m_##func##WasCalled)
|
|
|
|
class GlobalFileListener : public CommandQueue::FileListener
|
|
{
|
|
public:
|
|
virtual void onFileError(const FileHandle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{}
|
|
|
|
DEFINE_TEST_CALLBACK(onFileDeleted, FileHandle, 7);
|
|
DEFINE_TEST_CALLBACK(onFileLoaded, FileHandle, 1);
|
|
DEFINE_TEST_CALLBACK_ONE_PARAM(onArtboardsListed,
|
|
FileHandle,
|
|
2,
|
|
std::vector<std::string>,
|
|
artboardNames);
|
|
DEFINE_TEST_CALLBACK_ONE_PARAM(onViewModelsListed,
|
|
FileHandle,
|
|
3,
|
|
std::vector<std::string>,
|
|
viewModelNames);
|
|
|
|
DEFINE_TEST_CALLBACK_TWO_PARAM(onViewModelInstanceNamesListed,
|
|
FileHandle,
|
|
4,
|
|
std::string,
|
|
viewModelNameI,
|
|
std::vector<std::string>,
|
|
instanceNames);
|
|
DEFINE_TEST_CALLBACK_TWO_PARAM(
|
|
onViewModelPropertiesListed,
|
|
FileHandle,
|
|
5,
|
|
std::string,
|
|
viewModelNameP,
|
|
std::vector<CommandQueue::FileListener::ViewModelPropertyData>,
|
|
properties);
|
|
DEFINE_TEST_CALLBACK_ONE_PARAM(onViewModelEnumsListed,
|
|
FileHandle,
|
|
6,
|
|
std::vector<ViewModelEnum>,
|
|
enums);
|
|
|
|
FileHandle m_handle;
|
|
std::array<std::string, 3> m_artboardNames = {"Test Artboard",
|
|
"Test Transitions",
|
|
"Test Observation"};
|
|
std::array<std::string, 6> m_viewModelNames = {"Empty VM",
|
|
"Test All",
|
|
"Nested VM",
|
|
"State Transition",
|
|
"Alternate VM",
|
|
"Test Slash"};
|
|
std::array<std::string, 2> m_instanceNames = {"Test Default",
|
|
"Test Alternate"};
|
|
std::array<CommandQueue::FileListener::ViewModelPropertyData, 10>
|
|
m_properties = {
|
|
CommandQueue::FileListener::ViewModelPropertyData{
|
|
DataType::artboard,
|
|
"Test Artboard"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{DataType::list,
|
|
"Test List"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{
|
|
DataType::assetImage,
|
|
"Test Image"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{DataType::number,
|
|
"Test Num"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{DataType::string,
|
|
"Test String"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{
|
|
DataType::enumType,
|
|
"Test Enum",
|
|
"Test Enum Values"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{DataType::boolean,
|
|
"Test Bool"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{DataType::color,
|
|
"Test Color"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{DataType::trigger,
|
|
"Test Trigger"},
|
|
CommandQueue::FileListener::ViewModelPropertyData{
|
|
DataType::viewModel,
|
|
"Test Nested"}};
|
|
std::array<ViewModelEnum, 1> m_enums = {
|
|
ViewModelEnum{"Test Enum Values", {"Value 1", "Value 2"}}};
|
|
std::string m_viewModelNameI = "Test All";
|
|
std::string m_viewModelNameP = "Test All";
|
|
};
|
|
|
|
class GlobalRenderImageListener : public CommandQueue::RenderImageListener
|
|
{
|
|
public:
|
|
virtual void onRenderImageError(const RenderImageHandle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{}
|
|
|
|
DEFINE_TEST_CALLBACK(onRenderImageDeleted, RenderImageHandle, 8);
|
|
|
|
RenderImageHandle m_handle;
|
|
};
|
|
|
|
class GlobalAudioSourceListener : public CommandQueue::AudioSourceListener
|
|
{
|
|
public:
|
|
virtual void onAudioSourceError(const AudioSourceHandle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{}
|
|
|
|
DEFINE_TEST_CALLBACK(onAudioSourceDeleted, AudioSourceHandle, 9);
|
|
|
|
AudioSourceHandle m_handle;
|
|
};
|
|
|
|
class GlobalFontListener : public CommandQueue::FontListener
|
|
{
|
|
public:
|
|
virtual void onFontError(const FontHandle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{}
|
|
|
|
DEFINE_TEST_CALLBACK(onFontDeleted, FontHandle, 10);
|
|
|
|
FontHandle m_handle;
|
|
};
|
|
|
|
class GlobalArtboardListener : public CommandQueue::ArtboardListener
|
|
{
|
|
public:
|
|
virtual void onArtboardError(const ArtboardHandle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{}
|
|
|
|
DEFINE_TEST_CALLBACK(onArtboardDeleted, ArtboardHandle, 12);
|
|
DEFINE_TEST_CALLBACK_ONE_PARAM(onStateMachinesListed,
|
|
ArtboardHandle,
|
|
11,
|
|
std::vector<std::string>,
|
|
stateMachineNames);
|
|
DEFINE_TEST_CALLBACK_TWO_PARAM(onDefaultViewModelInfoReceived,
|
|
ArtboardHandle,
|
|
20,
|
|
std::string,
|
|
viewModelName,
|
|
std::string,
|
|
instanceName);
|
|
|
|
ArtboardHandle m_handle;
|
|
std::array<std::string, 1> m_stateMachineNames = {"SM"};
|
|
std::string m_viewModelName = "Test All";
|
|
std::string m_instanceName = "Test Default";
|
|
};
|
|
|
|
class GlobalViewModelInstanceListener
|
|
: public CommandQueue::ViewModelInstanceListener
|
|
{
|
|
public:
|
|
virtual void onViewModelInstanceError(const ViewModelInstanceHandle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{}
|
|
|
|
DEFINE_TEST_CALLBACK(onViewModelDeleted, ViewModelInstanceHandle, 15);
|
|
|
|
DEFINE_TEST_CALLBACK_ONE_PARAM(onViewModelDataReceived,
|
|
ViewModelInstanceHandle,
|
|
13,
|
|
CommandQueue::ViewModelInstanceData,
|
|
instanceData);
|
|
|
|
DEFINE_TEST_CALLBACK_TWO_PARAM(onViewModelListSizeReceived,
|
|
ViewModelInstanceHandle,
|
|
14,
|
|
std::string,
|
|
path,
|
|
size_t,
|
|
size);
|
|
|
|
size_t m_size = 0;
|
|
std::string m_path = "Test List";
|
|
ViewModelInstanceHandle m_handle;
|
|
CommandQueue::ViewModelInstanceData m_instanceData = {
|
|
.metaData = PropertyData{DataType::boolean, "Test Bool"},
|
|
.boolValue = true};
|
|
};
|
|
|
|
class GlobalStateMachineListener : public CommandQueue::StateMachineListener
|
|
{
|
|
public:
|
|
virtual void onStateMachineError(const StateMachineHandle,
|
|
uint64_t requestId,
|
|
std::string error) override
|
|
{}
|
|
|
|
DEFINE_TEST_CALLBACK(onStateMachineDeleted, StateMachineHandle, 17);
|
|
DEFINE_TEST_CALLBACK(onStateMachineSettled, StateMachineHandle, 16);
|
|
|
|
StateMachineHandle m_handle;
|
|
};
|
|
|
|
TEST_CASE("global Listener", "[CommandQueue]")
|
|
{
|
|
GlobalStateMachineListener globalStateMachineListener;
|
|
GlobalViewModelInstanceListener globalViewModelInstanceListener;
|
|
GlobalArtboardListener globalArtboardListener;
|
|
GlobalFontListener globalFontListener;
|
|
GlobalAudioSourceListener globalAudioSourceListener;
|
|
GlobalRenderImageListener globalRenderImageListener;
|
|
GlobalFileListener globalFileListener;
|
|
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
commandQueue->setGlobalFileListener(&globalFileListener);
|
|
commandQueue->setGlobalArtboardListener(&globalArtboardListener);
|
|
commandQueue->setGlobalStateMachineListener(&globalStateMachineListener);
|
|
commandQueue->setGlobalRenderImageListener(&globalRenderImageListener);
|
|
commandQueue->setGlobalAudioSourceListener(&globalAudioSourceListener);
|
|
commandQueue->setGlobalViewModelInstanceListener(
|
|
&globalViewModelInstanceListener);
|
|
commandQueue->setGlobalFontListener(&globalFontListener);
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
nullptr,
|
|
1);
|
|
auto artboardHandle = commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
auto stateMachineHandle =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle);
|
|
auto viewModel =
|
|
commandQueue->instantiateDefaultViewModelInstance(fileHandle,
|
|
artboardHandle);
|
|
|
|
std::ifstream imageStream("assets/batdude.png", std::ios::binary);
|
|
auto renderImage = commandQueue->decodeImage(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(imageStream), {}));
|
|
|
|
std::ifstream audioStream("assets/audio/what.wav", std::ios::binary);
|
|
auto audioSource = commandQueue->decodeAudio(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(audioStream), {}));
|
|
|
|
std::ifstream fontStream("assets/fonts/OpenSans-Italic.ttf",
|
|
std::ios::binary);
|
|
auto font = commandQueue->decodeFont(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(fontStream), {}));
|
|
|
|
globalFileListener.m_handle = fileHandle;
|
|
globalArtboardListener.m_handle = artboardHandle;
|
|
globalStateMachineListener.m_handle = stateMachineHandle;
|
|
globalViewModelInstanceListener.m_handle = viewModel;
|
|
globalRenderImageListener.m_handle = renderImage;
|
|
globalAudioSourceListener.m_handle = audioSource;
|
|
globalFontListener.m_handle = font;
|
|
|
|
// 1 is create file or fileLoaded callback
|
|
commandQueue->requestArtboardNames(fileHandle, 2);
|
|
commandQueue->requestViewModelNames(fileHandle, 3);
|
|
commandQueue->requestViewModelInstanceNames(fileHandle, "Test All", 4);
|
|
commandQueue->requestViewModelPropertyDefinitions(fileHandle,
|
|
"Test All",
|
|
5);
|
|
commandQueue->requestViewModelEnums(fileHandle, 6);
|
|
commandQueue->requestStateMachineNames(artboardHandle, 11);
|
|
commandQueue->requestDefaultViewModelInfo(artboardHandle, fileHandle, 20);
|
|
commandQueue->requestViewModelInstanceBool(viewModel, "Test Bool", 13);
|
|
commandQueue->requestViewModelInstanceListSize(viewModel, "Test List", 14);
|
|
commandQueue->advanceStateMachine(stateMachineHandle, 1, 16);
|
|
commandQueue->advanceStateMachine(stateMachineHandle, 1, 16);
|
|
commandQueue->advanceStateMachine(stateMachineHandle, 1, 16);
|
|
|
|
commandQueue->deleteFont(font, 10);
|
|
commandQueue->deleteStateMachine(stateMachineHandle, 17);
|
|
commandQueue->deleteArtboard(artboardHandle, 12);
|
|
commandQueue->deleteViewModelInstance(viewModel, 15);
|
|
commandQueue->deleteImage(renderImage, 8);
|
|
commandQueue->deleteFile(fileHandle, 7);
|
|
commandQueue->deleteAudio(audioSource, 9);
|
|
|
|
wait_for_server(commandQueue.get());
|
|
commandQueue->processMessages();
|
|
|
|
CHECK_CALLBACK(globalStateMachineListener, onStateMachineDeleted);
|
|
CHECK_CALLBACK(globalStateMachineListener, onStateMachineSettled);
|
|
|
|
CHECK_CALLBACK(globalViewModelInstanceListener, onViewModelDeleted);
|
|
CHECK_CALLBACK(globalViewModelInstanceListener, onViewModelDataReceived);
|
|
CHECK_CALLBACK(globalViewModelInstanceListener,
|
|
onViewModelListSizeReceived);
|
|
|
|
CHECK_CALLBACK(globalArtboardListener, onArtboardDeleted);
|
|
CHECK_CALLBACK(globalArtboardListener, onStateMachinesListed);
|
|
|
|
CHECK_CALLBACK(globalFileListener, onFileDeleted);
|
|
CHECK_CALLBACK(globalFileListener, onFileLoaded);
|
|
CHECK_CALLBACK(globalFileListener, onArtboardsListed);
|
|
CHECK_CALLBACK(globalFileListener, onViewModelsListed);
|
|
CHECK_CALLBACK(globalFileListener, onViewModelEnumsListed);
|
|
CHECK_CALLBACK(globalFileListener, onViewModelPropertiesListed);
|
|
CHECK_CALLBACK(globalFileListener, onViewModelInstanceNamesListed);
|
|
|
|
CHECK_CALLBACK(globalFontListener, onFontDeleted);
|
|
CHECK_CALLBACK(globalAudioSourceListener, onAudioSourceDeleted);
|
|
CHECK_CALLBACK(globalRenderImageListener, onRenderImageDeleted);
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
}
|
|
|
|
// StateMachines depend on Artboards and Artboards depend on Files.
|
|
// So if an artboard gets deleted so should the statemachines assosiated with
|
|
// it. If a file is deleted so should artboards and therefore statemachines.
|
|
|
|
TEST_CASE("dependency lifetime management", "[CommandQueue]")
|
|
{
|
|
auto commandQueue = make_rcp<CommandQueue>();
|
|
std::thread serverThread(server_thread, commandQueue);
|
|
|
|
std::ifstream stream("assets/data_bind_test_cmdq.riv", std::ios::binary);
|
|
FileHandle fileHandle = commandQueue->loadFile(
|
|
std::vector<uint8_t>(std::istreambuf_iterator<char>(stream), {}),
|
|
nullptr,
|
|
1);
|
|
|
|
auto artboardHandle = commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
auto artboardHandle2 = commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
auto artboardHandle3 = commandQueue->instantiateDefaultArtboard(fileHandle);
|
|
|
|
auto stateMachine =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle);
|
|
auto stateMachine_2 =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle);
|
|
|
|
auto stateMachine2 =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle2);
|
|
auto stateMachine2_2 =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle2);
|
|
|
|
auto stateMachine3 =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle2);
|
|
auto stateMachine3_2 =
|
|
commandQueue->instantiateDefaultStateMachine(artboardHandle2);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(server->getFile(fileHandle) != nullptr);
|
|
CHECK(server->getArtboardInstance(artboardHandle) != nullptr);
|
|
CHECK(server->getArtboardInstance(artboardHandle2) != nullptr);
|
|
CHECK(server->getArtboardInstance(artboardHandle3) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine_2) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine2) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine2_2) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine3) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine3_2) != nullptr);
|
|
});
|
|
|
|
commandQueue->deleteArtboard(artboardHandle);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(server->getFile(fileHandle) != nullptr);
|
|
CHECK(server->getArtboardInstance(artboardHandle) == nullptr);
|
|
CHECK(server->getArtboardInstance(artboardHandle2) != nullptr);
|
|
CHECK(server->getArtboardInstance(artboardHandle3) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine) == nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine_2) == nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine2) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine2_2) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine3) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine3_2) != nullptr);
|
|
});
|
|
|
|
commandQueue->deleteStateMachine(stateMachine2);
|
|
|
|
commandQueue->runOnce([&](CommandServer* server) {
|
|
CHECK(server->getFile(fileHandle) != nullptr);
|
|
CHECK(server->getArtboardInstance(artboardHandle) == nullptr);
|
|
CHECK(server->getArtboardInstance(artboardHandle2) != nullptr);
|
|
CHECK(server->getArtboardInstance(artboardHandle3) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine) == nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine_2) == nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine2) == nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine2_2) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine3) != nullptr);
|
|
CHECK(server->getStateMachineInstance(stateMachine3_2) != nullptr);
|
|
});
|
|
|
|
commandQueue->disconnect();
|
|
serverThread.join();
|
|
} |