Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding watch option using dmon #1308

Merged
merged 37 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
adf0172
Adding autoreload using dmon
mwestphal Mar 24, 2024
5bf1adf
Finishing up feature
mwestphal Mar 20, 2024
5965613
fixup
mwestphal Mar 20, 2024
5cc1725
Adding a test
mwestphal Mar 20, 2024
30dd814
Add missing license
mwestphal Mar 20, 2024
6fd133d
fix some tests
mwestphal Mar 20, 2024
096e2dc
Update application/F3DStarter.h
mwestphal Mar 21, 2024
252a5a0
Update application/F3DStarter.cxx
mwestphal Mar 21, 2024
85c0c4f
Update application/F3DStarter.h
mwestphal Mar 21, 2024
82e00bd
Add fix
mwestphal Mar 21, 2024
e94a8ba
fixup again
mwestphal Mar 21, 2024
3581731
fixup again
mwestphal Mar 21, 2024
aa3abb0
fixup windows
mwestphal Mar 21, 2024
21084de
Adding windows testing
mwestphal Mar 23, 2024
8a9bb7c
fixup windows
mwestphal Mar 23, 2024
2fd4468
formating fixup
mwestphal Mar 23, 2024
e95344a
try threading fix
mwestphal Mar 23, 2024
f333dc8
remove dmon from coverage
mwestphal Mar 23, 2024
a207346
fixup linux
mwestphal Mar 23, 2024
7849894
fixup again
mwestphal Mar 23, 2024
387d02b
fixup again
mwestphal Mar 23, 2024
a01ffcc
fixup atomic
mwestphal Mar 23, 2024
a936ff7
fixup again
mwestphal Mar 23, 2024
776a639
try lean and mean
mwestphal Mar 24, 2024
86eaf64
Revert "try lean and mean"
mwestphal Mar 24, 2024
1bbb6d0
Snoyer review
mwestphal Mar 24, 2024
7da2b56
Watch coverage test
mwestphal Mar 24, 2024
63f3b48
better testing logic
mwestphal Mar 24, 2024
fd4a6d5
Fixup to use xdotool
mwestphal Mar 24, 2024
89c070e
Add xdotool to docker ci
mwestphal Mar 24, 2024
ecec122
Fixup again
mwestphal Mar 24, 2024
d22a3ec
again
mwestphal Mar 24, 2024
50acbac
increase sleep time
mwestphal Mar 24, 2024
d3bddf7
less flakyness
mwestphal Mar 24, 2024
6a1a217
Revert "Add xdotool to docker ci"
mwestphal Mar 25, 2024
62de0b9
Improve doc
mwestphal Mar 25, 2024
e1d8811
Apply suggestions from code review
mwestphal Mar 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cppcheck.supp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ unknownMacro
// external libraries
*:external/nlohmann_json/nlohmann/json.hpp
*:external/cxxopts/cxxopts.hpp
*:external/dmon/dmon.h

// specific checks
knownConditionTrueFalse:library/testing/TestSDKImage.cxx
Expand Down
1 change: 1 addition & 0 deletions .github/actions/coverage-ci/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ runs:
lcov --remove coverage.info "*/dependencies/*" -o coverage.info
lcov --remove coverage.info "*/cxxopts.hpp" -o coverage.info
lcov --remove coverage.info "*/json.hpp" -o coverage.info
lcov --remove coverage.info "*/dmon.h" -o coverage.info
lcov --remove coverage.info "*Test*" -o coverage.info

- name: Upload coverage to Codecov
Expand Down
4 changes: 4 additions & 0 deletions .tsan.supp
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ race:libvtkCommonDataModel

# OpenEXR
race:libOpenEXR

# dmon
# https://github.com/septag/dmon/issues/33
race:dmon.h
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,17 @@ option(F3D_MODULE_EXR "OpenEXR images module" OFF)
# Use externals
option(F3D_USE_EXTERNAL_CXXOPTS "Use external cxxopts dependency" OFF)
option(F3D_USE_EXTERNAL_NLOHMANN_JSON "Use external nlohmann_json dependency" OFF)
option(F3D_USE_EXTERNAL_DMON "Use external dmon dependency" OFF)

if (F3D_USE_EXTERNAL_CXXOPTS)
find_package(cxxopts REQUIRED)
endif ()
if (F3D_USE_EXTERNAL_NLOHMANN_JSON)
find_package(nlohmann_json REQUIRED)
endif ()
if (F3D_USE_EXTERNAL_DMON)
find_package(dmon REQUIRED)
endif ()

# VTK dependency
# Optional components should list VTK modules
Expand Down
7 changes: 7 additions & 0 deletions application/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ elseif (MSVC)
endif ()

target_include_directories(f3d PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)

if (F3D_USE_EXTERNAL_CXXOPTS)
target_link_libraries(f3d PRIVATE cxxopts::cxxopts)
else ()
Expand All @@ -69,6 +70,12 @@ else ()
target_include_directories(f3d PUBLIC $<BUILD_INTERFACE:${F3D_SOURCE_DIR}/external/nlohmann_json>)
endif ()

if (F3D_USE_EXTERNAL_DMON)
target_link_libraries(f3d PRIVATE dmon::dmon)
else ()
target_include_directories(f3d PUBLIC $<BUILD_INTERFACE:${F3D_SOURCE_DIR}/external/dmon>)
endif ()

set(f3d_compile_options_private "")
set(f3d_compile_options_public "")
set(f3d_link_options_public "")
Expand Down
1 change: 1 addition & 0 deletions application/F3DOptionsParser.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ void ConfigurationOptions::GetOptions(F3DAppOptions& appOptions, f3d::options& o
this->DeclareOption(grp0, "dry-run", "", "Do not read the configuration file", appOptions.DryRun, HasDefault::YES, MayHaveConfig::NO );
this->DeclareOption(grp0, "no-render", "", "Do not render anything and quit right after loading the first file, use with --verbose to recover information about a file.", appOptions.NoRender, HasDefault::YES, MayHaveConfig::YES );
this->DeclareOption(grp0, "max-size", "", "Maximum size in Mib of a file to load, negative value means unlimited", appOptions.MaxSize, HasDefault::YES, MayHaveConfig::YES, "<size in Mib>");
this->DeclareOption(grp0, "watch", "", "Watch current file and automatically reload it whenever it is modified on disk", appOptions.Watch, HasDefault::YES, MayHaveConfig::YES );
this->DeclareOption(grp0, "load-plugins", "", "List of plugins to load separated with a comma", appOptions.Plugins, LocalHasDefaultNo, MayHaveConfig::YES, "<paths or names>");
this->DeclareOption(grp0, "scan-plugins", "", "Scan some directories for plugins (result can be incomplete)");

Expand Down
1 change: 1 addition & 0 deletions application/F3DOptionsParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct F3DAppOptions
std::string InteractionTestPlayFile = "";
bool NoBackground = false;
bool NoRender = false;
bool Watch = false;
double RefThreshold = 50;
double MaxSize = -1.0;

Expand Down
147 changes: 113 additions & 34 deletions application/F3DStarter.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,30 @@
#include "F3DOptionsParser.h"
#include "F3DSystemTools.h"

#define DMON_IMPL
#ifdef WIN32
#pragma warning(push)
#pragma warning(disable : 4505)
#include "dmon.h"
// dmon includes Windows.h which defines 'ERROR' and conflicts with log.h
mwestphal marked this conversation as resolved.
Show resolved Hide resolved
#undef ERROR
#pragma warning(pop)
#else
#include "dmon.h"
#endif

#include "engine.h"
#include "interactor.h"
#include "log.h"
#include "options.h"
#include "window.h"

#include <algorithm>
#include <atomic>
#include <cassert>
#include <filesystem>
#include <iostream>
#include <mutex>
#include <set>

namespace fs = std::filesystem;
Expand Down Expand Up @@ -133,15 +147,35 @@ class F3DStarter::F3DInternals
}
}

static void dmonFolderChanged(
dmon_watch_id, dmon_action, const char*, const char* filename, const char*, void* userData)
{
F3DStarter* self = reinterpret_cast<F3DStarter*>(userData);
const std::lock_guard<std::mutex> lock(self->Internals->FilesListMutex);
fs::path filePath = self->Internals->FilesList[self->Internals->CurrentFileIndex];
if (filePath.filename().string() == std::string(filename))
{
self->Internals->ReloadFileRequested = true;
}
}

F3DOptionsParser Parser;
F3DAppOptions AppOptions;
f3d::options DynamicOptions;
f3d::options FileOptions;
std::unique_ptr<f3d::engine> Engine;
std::vector<fs::path> FilesList;
int CurrentFileIndex = -1;
dmon_watch_id FolderWatchId;
bool LoadedFile = false;
bool UpdateWithCommandLineParsing = true;

// dmon used atomic and mutex
std::atomic<int> CurrentFileIndex = -1;
std::mutex FilesListMutex;

// Event loop atomics
std::atomic<bool> RenderRequested = false;
std::atomic<bool> ReloadFileRequested = false;
};

//----------------------------------------------------------------------------
Expand All @@ -151,10 +185,17 @@ F3DStarter::F3DStarter()
// Set option outside of command line and config file
this->Internals->DynamicOptions.set(
"ui.dropzone-info", "Drop a file or HDRI to load it\nPress H to show cheatsheet");

// Initialize dmon
dmon_init();
}

//----------------------------------------------------------------------------
F3DStarter::~F3DStarter() = default;
F3DStarter::~F3DStarter()
{
// deinit dmon
dmon_deinit();
}

//----------------------------------------------------------------------------
int F3DStarter::Start(int argc, char** argv)
Expand Down Expand Up @@ -219,41 +260,17 @@ int F3DStarter::Start(int argc, char** argv)
interactor.setKeyPressCallBack(
[this](int, const std::string& keySym) -> bool
{
const auto loadFile = [this](int index, bool restoreCamera = false) -> bool
{
this->Internals->Engine->getInteractor().stopAnimation();

f3d::log::debug("========== Loading 3D file ==========");

if (restoreCamera)
{
f3d::camera& cam = this->Internals->Engine->getWindow().getCamera();
const auto camState = cam.getState();
this->LoadFile(index, true);
cam.setState(camState);
}
else
{
this->LoadFile(index, true);
}

f3d::log::debug("========== Rendering ==========");

this->Render();
return true;
};

if (keySym == "Left")
{
return loadFile(-1);
return this->LoadRelativeFile(-1);
}
if (keySym == "Right")
{
return loadFile(+1);
return this->LoadRelativeFile(+1);
}
if (keySym == "Up")
{
return loadFile(0, true);
return this->LoadRelativeFile(0, true);
}
if (keySym == "Down")
{
Expand All @@ -264,7 +281,7 @@ int F3DStarter::Start(int argc, char** argv)
this->Internals->FilesList[static_cast<size_t>(this->Internals->CurrentFileIndex)]
.parent_path(),
true);
return loadFile(0);
return this->LoadRelativeFile(0);
}
return true;
}
Expand Down Expand Up @@ -299,7 +316,7 @@ int F3DStarter::Start(int argc, char** argv)
{
this->LoadFile(index);
}
this->Render();
this->RequestRender();
return true;
});
window
Expand Down Expand Up @@ -337,7 +354,6 @@ int F3DStarter::Start(int argc, char** argv)

if (!this->Internals->AppOptions.NoRender)
{
f3d::log::debug("========== Rendering ==========");
f3d::window& window = this->Internals->Engine->getWindow();
f3d::interactor& interactor = this->Internals->Engine->getInteractor();

Expand Down Expand Up @@ -460,7 +476,9 @@ int F3DStarter::Start(int argc, char** argv)
f3d::log::error("This is a headless build of F3D, interactive rendering is not supported");
return EXIT_FAILURE;
#else
this->Render();
// Create the event loop repeating timer
interactor.createTimerCallBack(30, [this]() { this->EventLoop(); });
this->RequestRender();
interactor.start();
#endif
}
Expand All @@ -472,6 +490,7 @@ int F3DStarter::Start(int argc, char** argv)
//----------------------------------------------------------------------------
void F3DStarter::LoadFile(int index, bool relativeIndex)
{
f3d::log::debug("========== Loading 3D file ==========");
// When loading a file, store any changed options
// into the dynamic options and use these dynamic option as the default
// for loading the file while still applying file specific options on top of it
Expand Down Expand Up @@ -621,7 +640,20 @@ void F3DStarter::LoadFile(int index, bool relativeIndex)
}
}

if (!this->Internals->LoadedFile)
if (this->Internals->LoadedFile)
{
if (this->Internals->AppOptions.Watch)
{
// Always unwatch and watch current folder, even on reload
if (this->Internals->FolderWatchId.id > 0)
{
dmon_unwatch(this->Internals->FolderWatchId);
}
this->Internals->FolderWatchId = dmon_watch(
filePath.parent_path().string().c_str(), &F3DInternals::dmonFolderChanged, 0, this);
}
}
else
{
// No file loaded, remove any previously loaded file
loader.loadGeometry("", true);
Expand All @@ -631,9 +663,17 @@ void F3DStarter::LoadFile(int index, bool relativeIndex)
this->Internals->Engine->getOptions().set("ui.filename-info", filenameInfo);
}

//----------------------------------------------------------------------------
void F3DStarter::RequestRender()
{
// Render will be called by the next event loop
this->Internals->RenderRequested = true;
}

//----------------------------------------------------------------------------
void F3DStarter::Render()
{
f3d::log::debug("========== Rendering ==========");
this->Internals->Engine->getWindow().render();
f3d::log::debug("Render done");
}
Expand Down Expand Up @@ -671,6 +711,8 @@ int F3DStarter::AddFile(const fs::path& path, bool quiet)

if (it == this->Internals->FilesList.end())
{
// In the main thread, we only need to guard writing
const std::lock_guard<std::mutex> lock(this->Internals->FilesListMutex);
this->Internals->FilesList.push_back(tmpPath);
return static_cast<int>(this->Internals->FilesList.size()) - 1;
}
Expand All @@ -684,3 +726,40 @@ int F3DStarter::AddFile(const fs::path& path, bool quiet)
}
}
}

//----------------------------------------------------------------------------
bool F3DStarter::LoadRelativeFile(int index, bool restoreCamera)
{
this->Internals->Engine->getInteractor().stopAnimation();

if (restoreCamera)
{
f3d::camera& cam = this->Internals->Engine->getWindow().getCamera();
const auto camState = cam.getState();
this->LoadFile(index, true);
cam.setState(camState);
}
else
{
this->LoadFile(index, true);
}

this->RequestRender();

return true;
}

//----------------------------------------------------------------------------
void F3DStarter::EventLoop()
{
if (this->Internals->ReloadFileRequested)
{
this->LoadRelativeFile(0, true);
this->Internals->ReloadFileRequested = false;
}
if (this->Internals->RenderRequested)
{
this->Render();
this->Internals->RenderRequested = false;
}
}
20 changes: 19 additions & 1 deletion application/F3DStarter.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ class F3DStarter
void LoadFile(int index = 0, bool relativeIndex = false);

/**
* Trigger a render
* Trigger a render on the next event loop
*/
void RequestRender();

/**
* Trigger a render immediately (must be called by the main thread)
*/
void Render();

Expand All @@ -47,6 +52,19 @@ class F3DStarter
private:
class F3DInternals;
std::unique_ptr<F3DInternals> Internals;

/**
* Internal method triggered when interacting with the application
* that load a file using relative index and handle camera restore
*/
bool LoadRelativeFile(int relativeIndex = 0, bool restoreCamera = false);

/**
* Internal event loop that is triggered repeatedly to handle specific events:
* - Render
* - ReloadFile
*/
void EventLoop();
};

#endif
Loading
Loading