engine: add init/project/timeline families to the C ABI facade
- oakengine_init/shutdown with HEADLESS/RENDER flags: headless boots Config, NodeFactory, ColorManager, task/conform/proxy/frame/disk managers and the serializer (plus an offscreen QGuiApplication that Qt requires for QAction); RENDER adds RenderManager. Idempotent and upgradable, no UI anywhere - oakengine_project_* (17): create/load/save, modified state, name, footage enumeration with online check, undo/redo, sequence access - oakengine_sequence_* (13): name, length (seconds and rational), frame rate, per-type track counts, playhead (timestamp and seconds), work area, markers; sequences are borrowed handles owned by their project - pure-C oakengine_init_test covers init idempotency, save/load round-trip through a real fixture project, footage online checks, timeline parameters, and NULL/bounds safety - no GL required
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAKENGINE_INIT_H
|
||||
#define OAKENGINE_INIT_H
|
||||
|
||||
#include "export.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file init.h
|
||||
* @brief C ABI for engine process initialization and shutdown
|
||||
*
|
||||
* oakengine_init() brings up the UI-independent engine services a consumer of
|
||||
* liboakengine needs before touching projects or timelines. It never creates
|
||||
* any UI: no QApplication, no main window, no display connection. When no
|
||||
* application object exists yet, an offscreen QGuiApplication is created (Qt
|
||||
* requires exactly one application object; it is leaked intentionally because
|
||||
* Qt cannot safely re-create one). A QGuiApplication -- not a plain
|
||||
* QCoreApplication -- is required because EngineCore's undo stack creates
|
||||
* QActions in its constructor, and Qt6 QActions need QGuiApplication state;
|
||||
* with the offscreen QPA plugin (the default, overridable through
|
||||
* QT_QPA_PLATFORM) nothing graphical ever happens.
|
||||
*
|
||||
* The flag mask selects the service set:
|
||||
*
|
||||
* - OAKENGINE_INIT_HEADLESS: Config, NodeFactory, ColorManager, TaskManager,
|
||||
* ConformManager, ProxyManager, FrameManager, DiskManager,
|
||||
* ProjectSerializer and a process-wide EngineCore shell (holds the global
|
||||
* undo stack and the EngineCore::instance() pointer engine code
|
||||
* dereferences). DiskManager is not part of EngineCore::start() but is
|
||||
* required because loading a project touches PlaybackCache state which
|
||||
* dereferences DiskManager::instance(). Everything here runs headless;
|
||||
* no GL is required.
|
||||
*
|
||||
* - OAKENGINE_INIT_RENDER: additionally creates the RenderManager. Only
|
||||
* with this bit may a consumer end up needing a GL context (the actual
|
||||
* render backends are dynamic engine plugins loaded on demand).
|
||||
*
|
||||
* Initialization is modelled on the render worker's headless bootstrap
|
||||
* (worker/workermain.cpp) rather than EngineCore::start(), because start()
|
||||
* unconditionally creates the RenderManager, starts the autorecovery timer
|
||||
* and reads the recent-projects list -- application behavior that does not
|
||||
* belong behind a library boundary.
|
||||
*
|
||||
* Conventions:
|
||||
* - Return codes: 0 (OAKENGINE_OK) on success, a negative OAKENGINE_E_*
|
||||
* error code on failure.
|
||||
* - oakengine_init() is idempotent: calling it again is a no-op for flag
|
||||
* bits already initialized and only brings up the missing bits (e.g.
|
||||
* upgrading HEADLESS to HEADLESS|RENDER).
|
||||
* - oakengine_shutdown() pairs with oakengine_init() and tears down the
|
||||
* initialized services in reverse order. The QCoreApplication and the
|
||||
* EngineCore shell are kept alive (see above), so oakengine_init() may be
|
||||
* called again afterwards.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Status and error codes shared by the init/project/timeline families.
|
||||
*/
|
||||
#define OAKENGINE_OK 0 /**< Success. */
|
||||
#define OAKENGINE_E_INVALID (-1) /**< NULL handle or invalid argument. */
|
||||
#define OAKENGINE_E_STATE (-2) /**< Call not valid in the current state. */
|
||||
#define OAKENGINE_E_FAILED (-3) /**< The engine reported a failure. */
|
||||
#define OAKENGINE_E_NOT_FOUND (-4) /**< Index out of range / entry not found. */
|
||||
|
||||
/** @brief Base headless engine services (no GL required). */
|
||||
#define OAKENGINE_INIT_HEADLESS 0x01
|
||||
/** @brief Render services on top of HEADLESS (may require GL). */
|
||||
#define OAKENGINE_INIT_RENDER 0x02
|
||||
|
||||
/**
|
||||
* @brief Initialize the engine services selected by `flags`.
|
||||
*
|
||||
* `flags` is a bitmask of OAKENGINE_INIT_HEADLESS and/or
|
||||
* OAKENGINE_INIT_RENDER; 0 is invalid. Repeated calls are idempotent and may
|
||||
* add the RENDER bit to a running HEADLESS instance.
|
||||
*
|
||||
* @return OAKENGINE_OK on success, OAKENGINE_E_INVALID for an empty mask.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_init(int flags);
|
||||
|
||||
/**
|
||||
* @brief Tear down the services brought up by oakengine_init().
|
||||
*
|
||||
* Safe to call when not initialized (a no-op then). The QCoreApplication and
|
||||
* the EngineCore shell survive shutdown intentionally.
|
||||
*
|
||||
* @return OAKENGINE_OK.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_shutdown(void);
|
||||
|
||||
/**
|
||||
* @brief Current initialization state as a flag bitmask (0 = not initialized).
|
||||
*
|
||||
* Only services that are actually up are reported, e.g. after upgrading a
|
||||
* HEADLESS instance with the RENDER bit the result includes both bits.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_init_flags(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OAKENGINE_INIT_H */
|
||||
@@ -0,0 +1,202 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAKENGINE_PROJECT_H
|
||||
#define OAKENGINE_PROJECT_H
|
||||
|
||||
#include "export.h"
|
||||
#include "init.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file project.h
|
||||
* @brief C ABI for Oak project files (.ove)
|
||||
*
|
||||
* An OakEngineProject wraps the engine's olive::Project node graph. Projects
|
||||
* are created empty with oakengine_project_create() and must then be given
|
||||
* content exactly once: either oakengine_project_new() for a blank project or
|
||||
* oakengine_project_load() to read a project file (the engine's project
|
||||
* serializers require a fresh, uninitialized project, so loading into a
|
||||
* project that already has content is rejected with OAKENGINE_E_STATE).
|
||||
*
|
||||
* Sequences are owned by their project through Qt's QObject parent chain;
|
||||
* OakEngineSequence handles are borrowed views whose lifetime follows the
|
||||
* project. See timeline.h for the sequence function family.
|
||||
*
|
||||
* Conventions (matching oakengine/ipc.h):
|
||||
* - Booleans are int (1/0).
|
||||
* - Return codes: 0 (OAKENGINE_OK) on success, negative OAKENGINE_E_* on
|
||||
* failure.
|
||||
* - String output uses the buf/size convention: the return value is the
|
||||
* number of characters that would have been written excluding the NUL,
|
||||
* so buf == NULL or a short buffer queries the required size. The output
|
||||
* is NUL-terminated whenever buf_size > 0. A negative return value is an
|
||||
* OAKENGINE_E_* error code.
|
||||
* - NULL handles are accepted everywhere and yield a no-op / zero result /
|
||||
* OAKENGINE_E_INVALID.
|
||||
*
|
||||
* Undo/redo note: the engine's undo stack is a process-wide singleton held by
|
||||
* EngineCore (EngineCore::undo_stack()), not a per-project object. The
|
||||
* project_undo/project_redo family therefore operates on that global stack
|
||||
* and is not scoped to `self`; oakengine_project_new() and
|
||||
* oakengine_project_load() clear it, matching the application's
|
||||
* new/open-project behavior.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Opaque project handle. Owned by the caller; release with
|
||||
* oakengine_project_free().
|
||||
*/
|
||||
typedef struct OakEngineProject OakEngineProject;
|
||||
|
||||
/**
|
||||
* @brief Opaque sequence (timeline) handle.
|
||||
*
|
||||
* Typedef'd here so project.h and timeline.h can be included in any order.
|
||||
* Handles are borrowed from their owning OakEngineProject (QObject parent
|
||||
* chain) and must NOT be freed; they become invalid when the project is
|
||||
* freed. The sequence function family lives in oakengine/timeline.h.
|
||||
*/
|
||||
typedef struct OakEngineSequence OakEngineSequence;
|
||||
|
||||
/**
|
||||
* @brief Allocate an empty project shell. Owned by the caller.
|
||||
*
|
||||
* The project has no content until exactly one of oakengine_project_new() or
|
||||
* oakengine_project_load() succeeds on it.
|
||||
*/
|
||||
OAKENGINE_API OakEngineProject *oakengine_project_create(void);
|
||||
|
||||
/**
|
||||
* @brief Destroy a project and everything it owns (including sequences).
|
||||
* NULL-safe.
|
||||
*/
|
||||
OAKENGINE_API void oakengine_project_free(OakEngineProject *self);
|
||||
|
||||
/**
|
||||
* @brief Initialize `self` as a new, blank project (untitled, not modified).
|
||||
*
|
||||
* Clears the global undo stack. Returns OAKENGINE_E_STATE if the project
|
||||
* already has content (new/load may only be applied once).
|
||||
*/
|
||||
OAKENGINE_API int oakengine_project_new(OakEngineProject *self);
|
||||
|
||||
/**
|
||||
* @brief Load project content from a .ove file.
|
||||
*
|
||||
* On success the project's filename is set to `path` and the modified flag is
|
||||
* cleared; the global undo stack is cleared. Footage is validated like the
|
||||
* engine does (EngineCore::validate_footage_in_loaded_project): files that
|
||||
* moved together with the project are resolved relatively; missing footage is
|
||||
* tolerated (no relink UI exists at this layer, so the project is accepted
|
||||
* as-is). On failure a human-readable reason is written to `err` using the
|
||||
* buf/size convention (err may be NULL) and a negative code is returned.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_project_load(OakEngineProject *self,
|
||||
const char *path, char *err,
|
||||
int err_size);
|
||||
|
||||
/**
|
||||
* @brief Save the project to `path`.
|
||||
*
|
||||
* `path` == NULL saves under the project's current filename
|
||||
* (Project::filename()); saving an untitled project without a path fails with
|
||||
* OAKENGINE_E_INVALID. Files are gzip-compressed unless the target name ends
|
||||
* in ".ovexml" (mirrors the application behavior). On success the project's
|
||||
* filename is set to the target and the modified flag is cleared
|
||||
* (EngineCore::on_project_saved() semantics, minus the UI recent-list).
|
||||
*/
|
||||
OAKENGINE_API int oakengine_project_save(OakEngineProject *self,
|
||||
const char *path);
|
||||
|
||||
OAKENGINE_API int oakengine_project_is_modified(const OakEngineProject *self);
|
||||
|
||||
/**
|
||||
* @brief Set the project modified flag (Project::set_modified).
|
||||
*/
|
||||
OAKENGINE_API int oakengine_project_set_modified(OakEngineProject *self,
|
||||
int modified);
|
||||
|
||||
/**
|
||||
* @brief Project display name (Project::name(): the filename's base name, or
|
||||
* "(untitled)"). Uses the buf/size convention.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_project_name(const OakEngineProject *self,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Full path the project was loaded from / saved to, or "" if untitled
|
||||
* (Project::filename()). Uses the buf/size convention.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_project_filename(const OakEngineProject *self,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Number of footage items in the project.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_project_footage_count(const OakEngineProject *self);
|
||||
|
||||
/**
|
||||
* @brief Stored filename of the footage item at `index` (Footage::filename(),
|
||||
* as recorded in the project file; may be relative). Uses the buf/size
|
||||
* convention; returns OAKENGINE_E_NOT_FOUND for an out-of-range index.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_project_footage_filename(
|
||||
const OakEngineProject *self, int index, char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief 1 if the footage file at `index` exists on disk, 0 if not.
|
||||
*
|
||||
* Mirrors the engine's validation rule: the stored path is checked as-is
|
||||
* first, then resolved relative to the project file's directory (footage
|
||||
* that moved together with the project stays online). Returns
|
||||
* OAKENGINE_E_NOT_FOUND for an out-of-range index.
|
||||
*/
|
||||
OAKENGINE_API int
|
||||
oakengine_project_footage_is_online(const OakEngineProject *self, int index);
|
||||
|
||||
/* ---- Undo (global singleton stack, see the file comment above) ---------- */
|
||||
|
||||
OAKENGINE_API int oakengine_project_can_undo(const OakEngineProject *self);
|
||||
OAKENGINE_API int oakengine_project_can_redo(const OakEngineProject *self);
|
||||
OAKENGINE_API int oakengine_project_undo(OakEngineProject *self);
|
||||
OAKENGINE_API int oakengine_project_redo(OakEngineProject *self);
|
||||
|
||||
/**
|
||||
* @brief Number of sequences (timelines) in the project.
|
||||
*/
|
||||
OAKENGINE_API int
|
||||
oakengine_project_sequence_count(const OakEngineProject *self);
|
||||
|
||||
/**
|
||||
* @brief Borrowed handle of the sequence at `index`, or NULL when out of
|
||||
* range. Do NOT free; the sequence is owned by the project.
|
||||
*/
|
||||
OAKENGINE_API OakEngineSequence *
|
||||
oakengine_project_sequence_at(const OakEngineProject *self, int index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OAKENGINE_PROJECT_H */
|
||||
@@ -0,0 +1,185 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAKENGINE_TIMELINE_H
|
||||
#define OAKENGINE_TIMELINE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "export.h"
|
||||
#include "init.h"
|
||||
#include "project.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file timeline.h
|
||||
* @brief C ABI for sequences (Oak timelines)
|
||||
*
|
||||
* An OakEngineSequence wraps the engine's olive::Sequence node
|
||||
* (engine/node/project/sequence/sequence.h, a ViewerOutput). This family is
|
||||
* intentionally read-mostly this round: playhead and workarea are simple
|
||||
* writes, everything else is inspection. Clip/track editing is a later
|
||||
* milestone.
|
||||
*
|
||||
* Handles are borrowed from their owning OakEngineProject (Qt QObject parent
|
||||
* chain; sequences are added to the project graph which becomes their
|
||||
* parent). There is deliberately no oakengine_sequence_free(): a sequence
|
||||
* handle becomes invalid when its project is freed. Sequences created with
|
||||
* oakengine_sequence_new() are additionally undoable -- undoing the creation
|
||||
* removes the sequence from the project (but keeps it alive under the undo
|
||||
* command), redoing brings it back.
|
||||
*
|
||||
* Time is exposed in the two representations used across the engine
|
||||
* (olive::core::Timecode terminology, core/util/timecodefunctions.h):
|
||||
*
|
||||
* - seconds: a plain double (`*_seconds` accessors), or a rational seconds
|
||||
* value as a numerator/denominator int pair (`*_rational`);
|
||||
*
|
||||
* - timestamp: an int64 count of timebase units, where the timebase is the
|
||||
* sequence's frame duration (the frame rate flipped, e.g. 1001/30000 for
|
||||
* a 30000/1001 sequence) -- i.e. a frame number. This matches how the
|
||||
* engine converts between Rational times and frame timestamps
|
||||
* (Timecode::time_to_timestamp / timestamp_to_time).
|
||||
*
|
||||
* Conventions match oakengine/project.h: booleans are int, 0
|
||||
* (OAKENGINE_OK)/negative OAKENGINE_E_* return codes, buf/size string output,
|
||||
* NULL handles yield no-ops or OAKENGINE_E_INVALID.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Create a new sequence named `name` in `project` and return its
|
||||
* borrowed handle (NULL on failure; `project` NULL -> NULL).
|
||||
*
|
||||
* The sequence gets the application's default parameters
|
||||
* (ViewerOutput::set_default_parameters(): width/height/pixel aspect/
|
||||
* interlacing/audio layout from Config, frame rate from the
|
||||
* DefaultSequenceFrameRate config entry, 30000/1001 by default) and starts
|
||||
* with zero tracks. The creation is pushed onto the global undo stack like
|
||||
* the application's "Create New Sequence" action (minus opening a viewer).
|
||||
*/
|
||||
OAKENGINE_API OakEngineSequence *
|
||||
oakengine_sequence_new(OakEngineProject *project, const char *name);
|
||||
|
||||
/**
|
||||
* @brief Sequence name (Node::get_label()). Uses the buf/size convention.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_sequence_name(const OakEngineSequence *self,
|
||||
char *buf, int buf_size);
|
||||
|
||||
/**
|
||||
* @brief Length of the sequence content in seconds
|
||||
* (ViewerOutput::get_length()). 0 for an empty sequence.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_sequence_get_length(const OakEngineSequence *self,
|
||||
double *seconds);
|
||||
|
||||
/**
|
||||
* @brief Length of the sequence content as rational seconds
|
||||
* (ViewerOutput::get_length().numerator()/denominator()).
|
||||
*/
|
||||
OAKENGINE_API int
|
||||
oakengine_sequence_get_length_rational(const OakEngineSequence *self, int *num,
|
||||
int *den);
|
||||
|
||||
/**
|
||||
* @brief Sequence frame rate as a num/den pair, e.g. 30000/1001
|
||||
* (ViewerOutput::get_video_params().frame_rate()).
|
||||
*/
|
||||
OAKENGINE_API int
|
||||
oakengine_sequence_get_frame_rate(const OakEngineSequence *self, int *num,
|
||||
int *den);
|
||||
|
||||
/**
|
||||
* @brief Number of tracks per track type (Sequence::track_list(type)->
|
||||
* get_track_count()). Any of `video`/`audio`/`subtitle` may be NULL.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_sequence_track_count(const OakEngineSequence *self,
|
||||
int *video, int *audio,
|
||||
int *subtitle);
|
||||
|
||||
/**
|
||||
* @brief Playhead position as a timestamp in timebase units (frame number;
|
||||
* ViewerOutput::get_playhead() rescaled to the frame-rate timebase).
|
||||
*/
|
||||
OAKENGINE_API int
|
||||
oakengine_sequence_get_playhead(const OakEngineSequence *self,
|
||||
int64_t *timestamp);
|
||||
|
||||
/**
|
||||
* @brief Move the playhead to `timestamp` (frame number;
|
||||
* ViewerOutput::set_playhead()).
|
||||
*/
|
||||
OAKENGINE_API int oakengine_sequence_set_playhead(OakEngineSequence *self,
|
||||
int64_t timestamp);
|
||||
|
||||
/**
|
||||
* @brief Playhead position in seconds.
|
||||
*/
|
||||
OAKENGINE_API int
|
||||
oakengine_sequence_get_playhead_seconds(const OakEngineSequence *self,
|
||||
double *seconds);
|
||||
|
||||
/**
|
||||
* @brief 1 if the workarea (in/out range) is enabled
|
||||
* (TimelineWorkArea::enabled()).
|
||||
*/
|
||||
OAKENGINE_API int
|
||||
oakengine_sequence_workarea_is_enabled(const OakEngineSequence *self);
|
||||
|
||||
/**
|
||||
* @brief Workarea in/out points as timestamps in timebase units
|
||||
* (TimelineWorkArea::in()/out()). Either pointer may be NULL.
|
||||
*/
|
||||
OAKENGINE_API int
|
||||
oakengine_sequence_get_workarea(const OakEngineSequence *self, int64_t *in,
|
||||
int64_t *out);
|
||||
|
||||
/**
|
||||
* @brief Set the workarea: enable flag plus in/out timestamps in timebase
|
||||
* units (TimelineWorkArea::set_enabled()/set_range()).
|
||||
*/
|
||||
OAKENGINE_API int oakengine_sequence_set_workarea(OakEngineSequence *self,
|
||||
int enabled, int64_t in,
|
||||
int64_t out);
|
||||
|
||||
/**
|
||||
* @brief Number of timeline markers (TimelineMarkerList::size()).
|
||||
*/
|
||||
OAKENGINE_API int
|
||||
oakengine_sequence_marker_count(const OakEngineSequence *self);
|
||||
|
||||
/**
|
||||
* @brief Marker at `index`: `time` receives its in-point as a timestamp in
|
||||
* timebase units (may be NULL), `name` its label using the buf/size
|
||||
* truncation convention (may be NULL to only fetch the time). Returns
|
||||
* OAKENGINE_OK on success, OAKENGINE_E_NOT_FOUND for an out-of-range index.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_sequence_marker_at(const OakEngineSequence *self,
|
||||
int index, int64_t *time,
|
||||
char *name, int name_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OAKENGINE_TIMELINE_H */
|
||||
Reference in New Issue
Block a user