Skip to content

Commit

Permalink
Implement proper event loop in libf3d (#1770)
Browse files Browse the repository at this point in the history
 - Add an event loop API to interactor
 - Remove timer API from interactor
 - Rework animation tick logic to rely on event loop
 - Implement watch logic to rely on event loop
 - Integrate UI event loop into event loop
 - Add proper  frame_rate support in interactor API
 - Removed libf3d option `render.animation.frame_rate`
 - Replaced `--animation-frame-rate` by more generic option `frame-rate`
 - Added a test for `frame_rate`
 - Updated doc
 - Fix #1534

Co-authored-by: Michael MIGLIORE <[email protected]>
  • Loading branch information
mwestphal and Meakk authored Dec 16, 2024
1 parent b0f676f commit df62746
Show file tree
Hide file tree
Showing 43 changed files with 618 additions and 311 deletions.
4 changes: 2 additions & 2 deletions application/F3DOptionsTools.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ static inline const std::array<CLIGroup, 8> CLIOptions = {{
{ "rendering-backend", "", "Backend to use when rendering (auto|glx|wgl|egl|osmesa)", "<string>", "" },
{ "max-size", "", "Maximum size in Mib of a file to load, negative value means unlimited", "<size in Mib>", "" },
{ "watch", "", "Watch current file and automatically reload it whenever it is modified on disk", "<bool>", "1" },
{ "frame-rate", "", "Frame rate used to refresh animation and other repeated tasks (watch, UI). Does not impact rendering frame rate.", "<fps>", "" },
{ "load-plugins", "", "List of plugins to load separated with a comma", "<paths or names>", "" },
{ "scan-plugins", "", "Scan standard directories for plugins and display available plugins (result can be incomplete)", "", "" },
{ "screenshot-filename", "", "Screenshot filename", "<filename>", "" } } },
Expand All @@ -94,7 +95,6 @@ static inline const std::array<CLIGroup, 8> CLIOptions = {{
{ "animation-index", "", "Select animation to show", "<index>", "" },
{ "animation-speed-factor", "", "Set animation speed factor", "<factor>", "" },
{ "animation-time", "", "Set animation time to load", "<time>", "" },
{"animation-frame-rate", "", "Set animation frame rate when playing animation interactively", "<frame rate>", ""},
{"font-file", "", "Path to a FreeType compatible font file", "<file_path>", ""},
{"command-script", "", "Path to a script file containing commands to execute", "<file_path>", "" } } },
{ "Material",
Expand Down Expand Up @@ -122,7 +122,7 @@ static inline const std::array<CLIGroup, 8> CLIOptions = {{
{ {"bg-color", "", "Background color", "<R,G,B>", ""},
{"resolution", "", "Window resolution", "<width,height>", ""},
{"position", "", "Window position", "<x,y>", ""},
{"fps", "z", "Display frame per second", "<bool>", "1"},
{"fps", "z", "Display rendering frame per second", "<bool>", "1"},
{"filename", "n", "Display filename", "<bool>", "1"},
{"metadata", "m", "Display file metadata", "<bool>", "1"},
{"blur-background", "u", "Blur background", "<bool>", "1" },
Expand Down
2 changes: 1 addition & 1 deletion application/F3DOptionsTools.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ static inline const OptionsDict DefaultAppOptions = {
{ "interaction-test-record", "" },
{ "interaction-test-play", "" },
{ "command-script", "" },
{ "frame-rate", "30.0" },
};

/**
Expand All @@ -78,7 +79,6 @@ static inline const std::map<std::string_view, std::string_view> LibOptionsNames
{ "animation-autoplay", "scene.animation.autoplay" },
{ "animation-index", "scene.animation.index" },
{ "animation-speed-factor", "scene.animation.speed_factor" },
{ "animation-frame-rate", "scene.animation.frame_rate" },
{ "font-file", "ui.font_file" },
{ "point-sprites", "model.point_sprites.enable" },
{ "point-sprites-type", "model.point_sprites.type" },
Expand Down
24 changes: 6 additions & 18 deletions application/F3DStarter.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class F3DStarter::F3DInternals
double MaxSize;
std::optional<double> AnimationTime;
bool Watch;
double FrameRate;
std::vector<std::string> Plugins;
std::string ScreenshotFilename;
std::string VerboseLevel;
Expand Down Expand Up @@ -546,6 +547,7 @@ class F3DStarter::F3DInternals
{
this->AppOptions.AnimationTime = f3d::options::parse<double>(appOptions.at("animation-time"));
}
this->AppOptions.FrameRate = f3d::options::parse<double>(appOptions.at("frame-rate"));
this->AppOptions.Watch = f3d::options::parse<bool>(appOptions.at("watch"));
this->AppOptions.Plugins = { f3d::options::parse<std::vector<std::string>>(
appOptions.at("load-plugins")) };
Expand Down Expand Up @@ -673,7 +675,6 @@ class F3DStarter::F3DInternals
interactor.addBinding({ mod_t::CTRL, "O" }, "open_file_dialog", "Others", std::bind(docString, "Open File Dialog"));
interactor.addBinding({ mod_t::CTRL, "F12" }, "take_minimal_screenshot", "Others", std::bind(docString, "Take a minimal screenshot"));


// This replace an existing default binding command in the libf3d
interactor.removeBinding({ mod_t::NONE, "Drop" });
interactor.addBinding({ mod_t::NONE, "Drop" }, "add_files_or_set_hdri", "Others", std::bind(docString, "Load dropped files, folder or HDRI"));
Expand Down Expand Up @@ -762,7 +763,6 @@ class F3DStarter::F3DInternals
std::mutex LoadedFilesMutex;

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

Expand Down Expand Up @@ -842,6 +842,7 @@ int F3DStarter::Start(int argc, char** argv)

f3d::log::debug("========== Configuring engine ==========");

double deltaTime = 1.0 / this->Internals->AppOptions.FrameRate;
const std::string& reference = this->Internals->AppOptions.Reference;
const std::string& output = this->Internals->AppOptions.Output;

Expand Down Expand Up @@ -929,7 +930,7 @@ int F3DStarter::Start(int argc, char** argv)
{
// For better testing, render once before the interaction
window.render();
if (!interactor.playInteraction(interactionTestPlayFile))
if (!interactor.playInteraction(interactionTestPlayFile, deltaTime))
{
return EXIT_FAILURE;
}
Expand Down Expand Up @@ -1079,14 +1080,13 @@ int F3DStarter::Start(int argc, char** argv)
{
// Create the event loop repeating timer
window.render();
interactor.createTimerCallBack(30, [this]() { this->EventLoop(); });

// gracefully exits if SIGTERM or SIGINT is send to F3D
GlobalInteractor = &interactor;
std::signal(SIGTERM, F3DInternals::SigCallback);
std::signal(SIGINT, F3DInternals::SigCallback);

interactor.start();
interactor.start(deltaTime, [this]() { this->EventLoop(); });
}
#endif
}
Expand Down Expand Up @@ -1342,13 +1342,6 @@ void F3DStarter::LoadFileGroup(
options.ui.filename_info = filenameInfo;
}

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

//----------------------------------------------------------------------------
void F3DStarter::Render()
{
Expand Down Expand Up @@ -1511,7 +1504,7 @@ bool F3DStarter::LoadRelativeFileGroup(int index, bool restoreCamera, bool force
this->LoadFileGroup(index, true, forceClear);
}

this->RequestRender();
this->Internals->Engine->getInteractor().requestRender();

return true;
}
Expand All @@ -1524,11 +1517,6 @@ void F3DStarter::EventLoop()
this->LoadRelativeFileGroup(0, true, true);
this->Internals->ReloadFileRequested = false;
}
if (this->Internals->RenderRequested)
{
this->Render();
this->Internals->RenderRequested = false;
}
}

//----------------------------------------------------------------------------
Expand Down
11 changes: 5 additions & 6 deletions application/testing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -386,12 +386,11 @@ f3d_test(NAME TestVerboseAnimation DATA InterpolationTest.glb ARGS --verbose NO_
f3d_test(NAME TestVerboseAnimationIndexError DATA InterpolationTest.glb ARGS --animation-index=48 NO_BASELINE REGEXP "Specified animation index is greater than the highest possible animation index, enabling the first animation.")

# Test interactive animation and speed factor
f3d_test(NAME TestInteractionAnimationFast DATA InterpolationTest.glb ARGS --animation-speed-factor=1000 --animation-index=-1 INTERACTION NO_BASELINE)#Space;Space;
f3d_test(NAME TestInteractionAnimationSlow DATA InterpolationTest.glb ARGS --animation-speed-factor=0.01 --animation-index=-1 INTERACTION)#Space;Wait;Space;
f3d_test(NAME TestInteractionAnimationCameraMovement DATA CameraAnimated.glb ARGS --camera-index=0 --animation-speed-factor=1000 --animation-progress INTERACTION)#Space;MouseMovement;Space;

# Framerate test is a smoke test as event playing seems to break VTK timers
f3d_test(NAME TestInteractionAnimationFrameRate DATA InterpolationTest.glb ARGS --animation-frame-rate=2 --animation-index=-1 INTERACTION NO_BASELINE)#Space;Wait;Space;
f3d_test(NAME TestInteractionAnimation DATA f3d.glb ARGS --animation-progress INTERACTION)#Space;Wait;Space;
f3d_test(NAME TestInteractionAnimationFast DATA f3d.glb ARGS --animation-progress --animation-speed-factor=1.5 INTERACTION)#Space;Wait;Space;
f3d_test(NAME TestInteractionAnimationSlow DATA f3d.glb ARGS --animation-progress --animation-speed-factor=0.5 INTERACTION)#Space;Wait;Space;
f3d_test(NAME TestInteractionAnimationFrameRate DATA f3d.glb ARGS --animation-progress --frame-rate=1 INTERACTION)#Space;Wait;Space;
f3d_test(NAME TestInteractionAnimationCameraMovement DATA CameraAnimated.glb ARGS --camera-index=0 --animation-progress INTERACTION)#Space;MouseMovement;Space;

# A verbose test that needs animation index support
f3d_test(NAME TestVerboseAnimationWrongAnimationTimeHigh DATA BoxAnimated.gltf ARGS --animation-time=10 --verbose REGEXP "Animation time 10 is outside of range \\[0, 3\\.70833\\], using 3\\.70833" NO_BASELINE)
Expand Down
1 change: 1 addition & 0 deletions doc/libf3d/CLASSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ This API also lets you control the content of the cheatsheet.
Use `log::setVerboseLevel(log::VerboseLevel::DEBUG)` to print debug information on interaction and command use.

Of course, you can use `start` and `stop` to control the interactor behavior.
`start` lets you specify time for the event loop and a `std::function` to execute at each loop.

## Camera class

Expand Down
1 change: 0 additions & 1 deletion doc/libf3d/OPTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ scene.animation.autoplay|bool<br>false<br>load|Automatically start animation.|\-
scene.animation.index|int<br>0<br>load|Select the animation to load.<br>Any negative value means all animations (glTF only).<br>The default scene always has at most one animation.|\-\-animation-index
scene.animation.speed_factor|double<br>1<br>render|Set the animation speed factor to slow, speed up or even invert animation.|\-\-animation-speed-factor
scene.animation.time|double<br>optional<br>load|Set the animation time to load.|\-\-animation-time
scene.animation.frame_rate|double<br>60<br>render|Set the animation frame rate used to play the animation interactively.|\-\-animation-frame-rate
scene.camera.index|int<br>optional<br>load|Select the scene camera to use when available in the file.<br>The default scene always uses automatic camera.|\-\-camera-index
scene.up_direction|string<br>+Y<br>load|Define the Up direction. It impacts the grid, the axis, the HDRI and the camera.|\-\-up
scene.camera.orthographic|bool<br>optional<br>load|Set to true to force orthographic projection. Model specified by default, which is false if not specified.|\-\-camera\-orthographic
Expand Down
2 changes: 1 addition & 1 deletion doc/user/CONFIGURATION_FILE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ The following options <b> cannot </b> be set via config file:
`help`, `version`, `readers-list`, `config`, `dry-run` and `input`.

The following options <b>are only taken on the first load</b>:
`no-render`, `output`, `position`, `resolution` and all testing options.
`no-render`, `output`, `position`, `resolution`, `frame-rate` and all testing options.

Boolean options that have been turned on in the configuration file can be turned
off on the command line if needed, eg: `--point-sprites=false`.
Expand Down
4 changes: 2 additions & 2 deletions doc/user/OPTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Options|Default|Description
\-\-no-render||Do not render anything and quit just after loading the first file, use with \-\-verbose to recover information about a file.
\-\-max-size=\<size in MiB\>|-1|Prevent F3D to load a file bigger than the provided size in Mib, negative value means unlimited, useful for thumbnails.
\-\-watch||Watch current file and automatically reload it whenever it is modified on disk.
\-\-frame-rate=\<fps\>|30.0|Frame rate used to refresh animation and other repeated tasks (watch, UI). Does not impact rendering frame rate.
\-\-load-plugins=\<paths or names\>||List of plugins to load separated with a comma. Official plugins are `alembic`, `assimp`, `draco`, `exodus`, `occt`, `usd`, `vdb`. See [plugins](PLUGINS.md) for more info.
\-\-scan-plugins||Scan standard directories for plugins and display their names, results may be incomplete. See [plugins](PLUGINS.md) for more info.
\-\-screenshot-filename=\<png file\>|`{app}/{model}_{n}.png`|Filename to save [screenshots](INTERACTIONS.md#taking-screenshots) to. Can use [template variables](#filename-templating).
Expand All @@ -45,7 +46,6 @@ Options|Default|Description
\-\-animation-index=\<idx\>|0|Select the animation to show.<br>Any negative value means all animations (glTF only).<br>The default scene always has at most one animation.
\-\-animation-speed-factor=\<factor\>|1|Set the animation speed factor to slow, speed up or even invert animation time.
\-\-animation-time=\<factor\>||Set the animation time to load.
\-\-animation-frame-rate=\<factor\>|60|Set the animation frame rate used when playing animation interactively.
\-\-font-file=\<font file\>||Use the provided FreeType compatible font file to display text.<br>Can be useful to display non-ASCII filenames.
\-\-command-script=\<command script\>||Provide a script file containing a list of commands to be executed sequentially.<br>Allows automation of multiple commands or pre-defined tasks.

Expand Down Expand Up @@ -78,7 +78,7 @@ Options|Default|Description
\-\-bg-color=\<R,G,B\>|0.2, 0.2, 0.2|Set the window *background color*.<br>Ignored if *hdri* is set.
\-\-resolution=\<width,height\>|1000, 600|Set the *window resolution*.
\-\-position=\<x,y\>||Set the *window position* (top left corner) , in pixels, starting from the top left of your screens.
-z, \-\-fps||Display a *frame per second counter*.
-z, \-\-fps||Display a rendering *frame per second counter*.
-n, \-\-filename||Display the *name of the file* on top of the window.
-m, \-\-metadata||Display the *metadata*.
\-\-hdri-skybox||Show the HDRI as a skybox. Overrides \-\-bg-color and \-\-no-background.
Expand Down
8 changes: 4 additions & 4 deletions examples/libf3d/cpp/multi-files/main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ int main(int argc, char** argv)
f3d::window& win = eng.getWindow();
win.render();

// Create a timer to stop interaction after one second
// Start interaction and stop it after one second
f3d::interactor& inter = eng.getInteractor();
inter.createTimerCallBack(1000, [&inter]() { inter.stop(); });
inter.start(1, [&inter]() { inter.stop(); });

// Start interaction
inter.start();
// Actual call would look like this
// inter.start();

return EXIT_SUCCESS;
}
8 changes: 4 additions & 4 deletions examples/libf3d/cpp/render-interact/main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ int main(int argc, char** argv)
f3d::window& win = eng.getWindow();
win.render();

// Create a timer to stop interaction after one second
// Start interaction and stop it after one second
f3d::interactor& inter = eng.getInteractor();
inter.createTimerCallBack(1000, [&inter]() { inter.stop(); });
inter.start(1, [&inter]() { inter.stop(); });

// Start interaction
inter.start();
// Actual call would look like this
// inter.start();

return EXIT_SUCCESS;
}
8 changes: 4 additions & 4 deletions examples/libf3d/cpp/use-options-string/main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ int main(int argc, char** argv)
f3d::window& win = eng.getWindow();
win.render();

// Create a timer to stop interaction after one second
// Start interaction and stop it after one second
f3d::interactor& inter = eng.getInteractor();
inter.createTimerCallBack(1000, [&inter]() { inter.stop(); });
inter.start(1, [&inter]() { inter.stop(); });

// Start interaction
inter.start();
// Actual call would look like this
// inter.start();

return EXIT_SUCCESS;
}
8 changes: 4 additions & 4 deletions examples/libf3d/cpp/use-options-struct/main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ int main(int argc, char** argv)
f3d::window& win = eng.getWindow();
win.render();

// Create a timer to stop interaction after one second
// Start interaction and stop it after one second
f3d::interactor& inter = eng.getInteractor();
inter.createTimerCallBack(1000, [&inter]() { inter.stop(); });
inter.start(1, [&inter]() { inter.stop(); });

// Start interaction
inter.start();
// Actual call would look like this
// inter.start();

return EXIT_SUCCESS;
}
8 changes: 4 additions & 4 deletions examples/libf3d/cpp/use-options-variant/main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ int main(int argc, char** argv)
f3d::window& win = eng.getWindow();
win.render();

// Create a timer to stop interaction after one second
// Start interaction and stop it after one second
f3d::interactor& inter = eng.getInteractor();
inter.createTimerCallBack(1000, [&inter]() { inter.stop(); });
inter.start(1, [&inter]() { inter.stop(); });

// Start interaction
inter.start();
// Actual call would look like this
// inter.start();

return EXIT_SUCCESS;
}
4 changes: 0 additions & 4 deletions library/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@
"speed_factor": {
"type": "ratio",
"default_value": "1.0"
},
"frame_rate": {
"type": "double",
"default_value": "60.0"
}
},
"camera": {
Expand Down
18 changes: 12 additions & 6 deletions library/private/animationManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ class animationManager
return Playing;
}

/**
* Set the animation in delta time in seconds
*/
void SetDeltaTime(double deltaTime);

/**
* Advance animationTime of DeltaTime and call loadAtTime accordingly
* Do nothing if IsPlaying is false
*/
void Tick();

/**
* Load animation at provided time value
*/
Expand All @@ -101,11 +112,6 @@ class animationManager
std::pair<double, double> GetTimeRange();

private:
/**
* Called by an internal timer to advance one animation tick
*/
void Tick();

const options& Options;
window_impl& Window;
vtkImporter* Importer = nullptr;
Expand All @@ -114,8 +120,8 @@ class animationManager
double TimeRange[2] = { 0.0, 0.0 };
bool Playing = false;
bool HasAnimation = false;
unsigned long CallBackId = 0;
double CurrentTime = 0;
double DeltaTime = 0;
bool CurrentTimeSet = false;
int AnimationIndex = 0;
int AvailAnimations = -1;
Expand Down
15 changes: 10 additions & 5 deletions library/private/interactor_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ class interactor_impl : public interactor
std::pair<std::string, std::string> getBindingDocumentation(
const interaction_bind_t& bind) const override;

unsigned long createTimerCallBack(double time, std::function<void()> callBack) override;
void removeTimerCallBack(unsigned long id) override;

void toggleAnimation() override;
void startAnimation() override;
void stopAnimation() override;
Expand All @@ -67,11 +64,13 @@ class interactor_impl : public interactor
void enableCameraMovement() override;
void disableCameraMovement() override;

bool playInteraction(const std::string& file) override;
bool playInteraction(
const std::string& file, double deltaTime, std::function<void()> userCallBack) override;
bool recordInteraction(const std::string& file) override;

void start() override;
void start(double deltaTime, std::function<void()> userCallBack) override;
void stop() override;
void requestRender() override;
///@}

/**
Expand Down Expand Up @@ -101,6 +100,12 @@ class interactor_impl : public interactor
*/
void UpdateRendererAfterInteraction();

/**
* Event loop being called automatically once the interactor is started
* First call the EventLoopUserCallBack, then call render if requested.
*/
void EventLoop();

/**
* An exception that can be thrown by certain command callbacks
* when the arguments of the callback are incorrect and expected
Expand Down
6 changes: 6 additions & 0 deletions library/private/window_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ class window_impl : public window
*/
void SetInteractor(interactor_impl* interactor);

/**
* Trigger a render only of the UI
* Does nothing if F3D_MODULE_UI is OFF
*/
void RenderUIOnly();

private:
class internals;
std::unique_ptr<internals> Internals;
Expand Down
Loading

0 comments on commit df62746

Please sign in to comment.