Skip to content

Commit

Permalink
Event-driven architecture for braynsService, resolve #286 (#314)
Browse files Browse the repository at this point in the history
* use libuv and uvw as an event-loop
* extend Brayns to use threaded and event-driven rendering in braynsService
* braynsViewer remains untouched in its behavior
* introduce cancel for snapshots
  • Loading branch information
tribal-tec committed Mar 5, 2018
1 parent 68b4322 commit 9d203de
Show file tree
Hide file tree
Showing 44 changed files with 1,211 additions and 555 deletions.
2 changes: 1 addition & 1 deletion .github_changelog_generator
@@ -1,4 +1,4 @@
future-release=0.5.0
future-release=0.6.0
user=BlueBrain
project=Brayns
output=Changelog.md
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
@@ -1,3 +1,6 @@
[submodule "CMake/common"]
path = CMake/common
url = https://github.com/Eyescale/CMake
[submodule "uvw"]
path = uvw
url = https://github.com/skypjack/uvw.git
2 changes: 1 addition & 1 deletion .gitsubprojects
Expand Up @@ -2,7 +2,7 @@
git_subproject(vmmlib https://github.com/Eyescale/vmmlib.git d7681fe)

if(BRAYNS_NETWORKING_ENABLED)
git_subproject(Rockets https://github.com/BlueBrain/Rockets.git 8f0ef1c)
git_subproject(Rockets https://github.com/BlueBrain/Rockets.git b67cfad)
endif()

# Streaming to display walls
Expand Down
30 changes: 24 additions & 6 deletions CMakeLists.txt
Expand Up @@ -6,7 +6,7 @@
# This file is part of Brayns <https://github.com/BlueBrain/Brayns>

cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
project(Brayns VERSION 0.5.0)
project(Brayns VERSION 0.6.0)
set(Brayns_VERSION_ABI 1)

list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMake
Expand Down Expand Up @@ -37,11 +37,25 @@ common_find_package(Boost REQUIRED COMPONENTS
filesystem system program_options unit_test_framework)
common_find_package(vmmlib REQUIRED)
common_find_package(OpenMP)
common_find_package(libuv)

if(libuv_FOUND)
# The libuv version of Ubuntu 16.04 is too old to have this definition that is
# required by uvw (in fact, it uses an old enough version as well to make
# things work, but couldn't go further).
if(libuv_VERSION VERSION_LESS 1.9.0)
add_definitions(-DUV_DISCONNECT=4)
endif()
if(NOT EXISTS ${PROJECT_SOURCE_DIR}/uvw)
message(FATAL_ERROR "uvw missing, run: git submodule update --init")
endif()
include_directories(SYSTEM uvw/src)
endif()

# HTTP messaging
common_find_package(LibJpegTurbo)
common_find_package(Rockets)
if(LibJpegTurbo_FOUND AND TARGET Rockets)
if(TARGET Rockets)
option(BRAYNS_NETWORKING_ENABLED "Activate networking interfaces" ON)
if(BRAYNS_NETWORKING_ENABLED)
list(APPEND COMMON_FIND_PACKAGE_DEFINES BRAYNS_USE_NETWORKING)
Expand Down Expand Up @@ -139,9 +153,13 @@ if(BRAYNS_VIEWER_ENABLED)
add_subdirectory(apps/BraynsViewer)
endif()

option(BRAYNS_SERVICE_ENABLED "Brayns Service" ON)
if(BRAYNS_SERVICE_ENABLED)
add_subdirectory(apps/BraynsService)
if(libuv_FOUND)
option(BRAYNS_SERVICE_ENABLED "Brayns Service" ON)
if(BRAYNS_SERVICE_ENABLED)
add_subdirectory(apps/BraynsService)
endif()
else()
unset(BRAYNS_SERVICE_ENABLED)
endif()

option(BRAYNS_BENCHMARK_ENABLED "Brayns Benchmark" ON)
Expand All @@ -168,7 +186,7 @@ if(BRAYNS_OSPRAY_ENABLED)
add_dependencies(braynsOSPRayEnginePlugin ospray_module_optix)
endif()
endif()
add_subdirectory(ospray_modules/opendeck)
add_subdirectory(ospray_modules/opendeck)
endif()

set(DOXYGEN_EXTRA_INPUT "${PROJECT_SOURCE_DIR}/Changelog.md")
Expand Down
4 changes: 3 additions & 1 deletion Dockerfile
Expand Up @@ -29,6 +29,7 @@ RUN apt-get update \
libmagick++-dev \
libtbb-dev \
libturbojpeg0-dev \
libuv1-dev \
wget \
ca-certificates \
&& apt-get clean \
Expand Down Expand Up @@ -109,7 +110,7 @@ ADD . ${BRAYNS_SRC}
# https://github.com/BlueBrain/Brayns
RUN cksum ${BRAYNS_SRC}/.gitsubprojects \
&& cd ${BRAYNS_SRC} \
&& git submodule update --init --recursive --remote \
&& git submodule update --init --recursive \
&& mkdir -p build \
&& cd build \
&& PKG_CONFIG_PATH=${DIST_PATH}/lib/pkgconfig CMAKE_PREFIX_PATH=${DIST_PATH} cmake .. -GNinja \
Expand Down Expand Up @@ -141,6 +142,7 @@ RUN apt-get update \
libmagick++-6.q16-7 \
libmagickwand-6.q16-3 \
libturbojpeg0 \
libuv1 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

Expand Down
2 changes: 1 addition & 1 deletion apps/BraynsService/CMakeLists.txt
Expand Up @@ -7,7 +7,7 @@
set(BRAYNSSERVICE_SOURCES main.cpp)

set(BRAYNSSERVICE_LINK_LIBRARIES
PUBLIC brayns braynsCommon braynsIO braynsParameters
PUBLIC brayns braynsCommon braynsIO braynsParameters ${libuv_LIBRARIES}
)

common_application(braynsService)
174 changes: 170 additions & 4 deletions apps/BraynsService/main.cpp
Expand Up @@ -20,21 +20,187 @@

#include <brayns/Brayns.h>
#include <brayns/common/Timer.h>
#include <brayns/common/engine/Engine.h>
#include <brayns/common/log.h>
#include <brayns/common/renderer/Renderer.h>
#include <brayns/common/types.h>

#include <uvw.hpp>

#include <thread>

class BraynsService
{
public:
BraynsService(int argc, const char** argv)
: _brayns(argc, argv)
, _renderingDone{_mainLoop->resource<uvw::AsyncHandle>()}
, _eventRendering{_mainLoop->resource<uvw::IdleHandle>()}
, _accumRendering{_mainLoop->resource<uvw::IdleHandle>()}
, _progressUpdate{_mainLoop->resource<uvw::TimerHandle>()}
, _checkIdleRendering{_mainLoop->resource<uvw::CheckHandle>()}
, _triggerRendering{_renderLoop->resource<uvw::AsyncHandle>()}
, _stopRenderThread{_renderLoop->resource<uvw::AsyncHandle>()}
{
_checkIdleRendering->start();

_setupMainThread();
_setupRenderThread();

_brayns.createPlugins();
}

void run()
{
// Start render & main loop
std::thread renderThread(
[& _renderLoop = _renderLoop] { _renderLoop->run(); });
_mainLoop->run();

// Finished
renderThread.join();
}

private:
void _setupMainThread()
{
// triggered after rendering, send events to rockets
_renderingDone->on<uvw::AsyncEvent>([& brayns = _brayns](const auto&,
auto&) {
brayns.postRender();
});

// events from rockets, trigger rendering
_brayns.getEngine().triggerRender = [& eventRendering = _eventRendering]
{
eventRendering->start();
};

// render or data load trigger from events
_eventRendering->on<uvw::IdleEvent>([&](const auto&, auto&) {
_eventRendering->stop();
_accumRendering->stop();
_timeSinceLastEvent.start();

// stop event loop(s) and exit application
if (!_brayns.getEngine().getKeepRunning())
{
_stopRenderThread->send();
_mainLoop->stop();
return;
}

// data loading
if (_brayns.getEngine().rebuildScene())
{
if (_isLoading)
return;

_isLoading = true;

_checkIdleRendering->stop();

// broadcast progress updates every 100ms
_progressUpdate->start(std::chrono::milliseconds(0),
std::chrono::milliseconds(100));

// async load execution
auto work = _mainLoop->resource<uvw::WorkReq>(
[&] { _brayns.buildScene(); });

// async load finished, restore everything to continue rendering
work->template on<uvw::WorkEvent>([&](const auto&, auto&) {
_brayns.getEngine().markRebuildScene(false);
_progressUpdate->stop();
_progressUpdate->close();
_isLoading = false;

_checkIdleRendering->start();
_eventRendering->start();
});

work->queue();
return;
}

// rendering
if (_brayns.preRender())
_triggerRendering->send();
});

// send progress updates while we are loading
_progressUpdate->on<uvw::TimerEvent>([& brayns = _brayns](const auto&,
auto&) {
brayns.postRender();
});

// send final progress update, once loading is finished
_progressUpdate->on<uvw::CloseEvent>([& brayns = _brayns](const auto&,
auto&) {
brayns.postRender();
});

// start accum rendering when we have no more other events
_checkIdleRendering->on<uvw::CheckEvent>([& accumRendering =
_accumRendering](
const auto&, auto&) { accumRendering->start(); });

// accumulation rendering on idle; re-triggered by _checkIdleRendering
_accumRendering->on<uvw::IdleEvent>([&](const auto&, auto&) {
if (_timeSinceLastEvent.elapsed() < _idleRenderingDelay)
return;

if (_brayns.preRender() && _brayns.getEngine().continueRendering())
_triggerRendering->send();

_accumRendering->stop();
});
}

void _setupRenderThread()
{
// rendering, triggered from main thread
_triggerRendering->on<uvw::AsyncEvent>([&](const auto&, auto&) {
_brayns.renderOnly();
_renderingDone->send();
});

// stop render loop, triggered from main thread
_stopRenderThread->once<uvw::AsyncEvent>([& renderLoop = _renderLoop](
const auto&, auto&) { renderLoop->stop(); });
}

brayns::Brayns _brayns;

std::shared_ptr<uvw::Loop> _mainLoop{uvw::Loop::getDefault()};
std::shared_ptr<uvw::AsyncHandle> _renderingDone;
std::shared_ptr<uvw::IdleHandle> _eventRendering;
std::shared_ptr<uvw::IdleHandle> _accumRendering;
std::shared_ptr<uvw::TimerHandle> _progressUpdate;
std::shared_ptr<uvw::CheckHandle> _checkIdleRendering;

std::shared_ptr<uvw::Loop> _renderLoop{uvw::Loop::create()};
std::shared_ptr<uvw::AsyncHandle> _triggerRendering;
std::shared_ptr<uvw::AsyncHandle> _stopRenderThread;

const float _idleRenderingDelay{0.1f};
bool _isLoading{false};
brayns::Timer _timeSinceLastEvent;
};

int main(int argc, const char** argv)
{
try
{
BRAYNS_INFO << "Initializing Service..." << std::endl;
brayns::Brayns brayns(argc, argv);

brayns::Timer timer;
timer.start();
bool keepRunning = true;
while (keepRunning)
keepRunning = brayns.render();

BraynsService service(argc, argv);

service.run();

timer.stop();
BRAYNS_INFO << "Service was running for " << timer.seconds()
<< " seconds" << std::endl;
Expand Down
10 changes: 10 additions & 0 deletions apps/BraynsViewer/BraynsViewer.cpp
Expand Up @@ -22,13 +22,17 @@
#include "BraynsViewer.h"

#include <brayns/Brayns.h>
#include <brayns/common/engine/Engine.h>
#include <brayns/parameters/ParametersManager.h>

#include <thread>

namespace brayns
{
BraynsViewer::BraynsViewer(Brayns& brayns)
: BaseWindow(brayns)
{
brayns.createPlugins();
}

void BraynsViewer::display()
Expand All @@ -49,5 +53,11 @@ void BraynsViewer::display()
setTitle(ss.str());

BaseWindow::display();

if (_brayns.getEngine().rebuildScene())
{
_brayns.getEngine().markRebuildScene(false);
std::thread([& brayns = _brayns]() { brayns.buildScene(); }).detach();
}
}
}

0 comments on commit 9d203de

Please sign in to comment.