Files
OrcaSlicer/deps_src/mcut/include/mcut/internal/frontend.h
SoftFever 883607e1d4 Refactor folder (#10475)
Move many third-party components' source codes from the src folder to a new folder called deps_src. The goal is to make the code structure clearer and easier to navigate.
2025-08-22 20:02:26 +08:00

847 lines
33 KiB
C++

/**
* Copyright (c) 2021-2022 Floyd M. Chitalu.
* All rights reserved.
*
* NOTE: This file is licensed under GPL-3.0-or-later (default).
* A commercial license can be purchased from Floyd M. Chitalu.
*
* License details:
*
* (A) GNU General Public License ("GPL"); a copy of which you should have
* recieved with this file.
* - see also: <http://www.gnu.org/licenses/>
* (B) Commercial license.
* - email: floyd.m.chitalu@gmail.com
*
* The commercial license options is for users that wish to use MCUT in
* their products for comercial purposes but do not wish to release their
* software products under the GPL license.
*
* Author(s) : Floyd M. Chitalu
*/
/**
* @file mcut.h
* @author Floyd M. Chitalu
* @date 11 July 2022
*
* @brief API-function implementations.
*
* NOTE: This header file defines the pre- and post-cutting processing of mesh
* data, which includes any intermediate correctons/modifications to the user's
* input meshes like 'polygon partitioning'.
*
*/
#ifndef _FRONTEND_H_
#define _FRONTEND_H_
#include "mcut/mcut.h"
#include <future>
#include <map>
#include <memory>
#include <string>
#include <chrono>
#include "mcut/internal/tpool.h"
#include "mcut/internal/kernel.h"
/*
std::invalid_argument: related to the input parameters
std::runtime_error: system runtime error e.g. out of memory
std::logic_error: a bug caught through an assertion failure
std::exception: unknown error source e.g. probably another bug
*/
#define CATCH_POSSIBLE_EXCEPTIONS(logstr) \
catch (std::invalid_argument & e0) \
{ \
logstr = e0.what(); \
return_value = McResult::MC_INVALID_VALUE; \
} \
catch (std::runtime_error & e1) \
{ \
logstr = e1.what(); \
return_value = McResult::MC_INVALID_OPERATION; \
} \
catch (std::logic_error & e2) \
{ \
logstr = e2.what(); \
return_value = McResult::MC_RESULT_MAX_ENUM; \
} \
catch (std::exception & e3) \
{ \
logstr = e3.what(); \
return_value = McResult::MC_RESULT_MAX_ENUM; \
}
extern thread_local std::string per_thread_api_log_str; // frontend.cpp
extern "C" void create_context_impl(
McContext* pContext, McFlags flags, uint32_t num_helper_threads) noexcept(false);
extern "C" void debug_message_callback_impl(
McContext context,
pfn_mcDebugOutput_CALLBACK cb,
const McVoid* userParam) noexcept(false);
extern "C" void get_debug_message_log_impl(McContext context,
McUint32 count, McSize bufSize,
McDebugSource* sources, McDebugType* types, McDebugSeverity* severities,
McSize* lengths, McChar* messageLog, McUint32& numFetched);
extern "C" void debug_message_control_impl(
McContext context,
McDebugSource source,
McDebugType type,
McDebugSeverity severity,
bool enabled) noexcept(false);
extern "C" void get_info_impl(
const McContext context,
McFlags info,
McSize bytes,
McVoid* pMem,
McSize* pNumBytes) noexcept(false);
extern "C" void bind_state_impl(
const McContext context,
McFlags stateInfo,
McSize bytes,
const McVoid* pMem);
extern "C" void create_user_event_impl(McEvent* event, McContext context);
extern "C" void set_user_event_status_impl(McEvent event, McInt32 execution_status);
extern "C" void get_event_info_impl(
const McEvent event,
McFlags info,
McSize bytes,
McVoid* pMem,
McSize* pNumBytes) noexcept(false);
extern "C" void set_event_callback_impl(
McEvent eventHandle,
pfn_McEvent_CALLBACK eventCallback,
McVoid* data);
extern "C" void wait_for_events_impl(
uint32_t numEventsInWaitlist,
const McEvent* pEventWaitList,
McResult& runtimeStatusFromAllPrecedingEvents) noexcept(false);
extern "C" void dispatch_impl(
McContext context,
McFlags flags,
const McVoid* pSrcMeshVertices,
const uint32_t* pSrcMeshFaceIndices,
const uint32_t* pSrcMeshFaceSizes,
uint32_t numSrcMeshVertices,
uint32_t numSrcMeshFaces,
const McVoid* pCutMeshVertices,
const uint32_t* pCutMeshFaceIndices,
const uint32_t* pCutMeshFaceSizes,
uint32_t numCutMeshVertices,
uint32_t numCutMeshFaces,
uint32_t numEventsInWaitlist,
const McEvent* pEventWaitList,
McEvent* pEvent) noexcept(false);
extern "C" void dispatch_planar_section_impl(
McContext context,
McFlags flags,
const McVoid* pSrcMeshVertices,
const uint32_t* pSrcMeshFaceIndices,
const uint32_t* pSrcMeshFaceSizes,
uint32_t numSrcMeshVertices,
uint32_t numSrcMeshFaces,
const McDouble* pNormalVector,
const McDouble sectionOffset,
uint32_t numEventsInWaitlist,
const McEvent* pEventWaitList,
McEvent* pEvent) noexcept(false);
extern "C" void get_connected_components_impl(
const McContext context,
const McConnectedComponentType connectedComponentType,
const uint32_t numEntries,
McConnectedComponent* pConnComps,
uint32_t* numConnComps,
uint32_t numEventsInWaitlist,
const McEvent* pEventWaitList,
McEvent* pEvent) noexcept(false);
extern "C" void get_connected_component_data_impl(
const McContext context,
const McConnectedComponent connCompId,
McFlags flags,
McSize bytes,
McVoid* pMem,
McSize* pNumBytes,
uint32_t numEventsInWaitlist,
const McEvent* pEventWaitList,
McEvent* pEvent) noexcept(false);
extern "C" void release_connected_components_impl(
const McContext context,
uint32_t numConnComps,
const McConnectedComponent* pConnComps) noexcept(false);
extern "C" void release_context_impl(
McContext context) noexcept(false);
extern "C" void release_events_impl(uint32_t numEvents, const McEvent* pEvents);
// base struct from which other structs represent connected components inherit
struct connected_component_t {
virtual ~connected_component_t() {};
McConnectedComponentType type = (McConnectedComponentType)0;
McConnectedComponent m_user_handle = MC_NULL_HANDLE;
// array_mesh_t indexArrayMesh;
// hmesh_t mesh;
std::shared_ptr<output_mesh_info_t> kernel_hmesh_data;
//
std::shared_ptr< //
std::unordered_map< //
fd_t /*child face*/,
fd_t /*parent face in the [user-provided] source mesh*/
> //
>
source_hmesh_child_to_usermesh_birth_face; // fpPartitionChildFaceToCorrespondingInputSrcMeshFace
std::shared_ptr< //
std::unordered_map< //
fd_t /*child face*/,
fd_t /*parent face in the [user-provided] cut mesh*/
>>
cut_hmesh_child_to_usermesh_birth_face; // fpPartitionChildFaceToCorrespondingInputCutMeshFace
// descriptors and coordinates of new vertices that are added into an input mesh (source mesh or cut mesh)
// in order to carry out partitioning
std::shared_ptr<std::unordered_map<vd_t, vec3>> source_hmesh_new_poly_partition_vertices; // addedFpPartitioningVerticesOnCorrespondingInputSrcMesh
std::shared_ptr<std::unordered_map<vd_t, vec3>> cut_hmesh_new_poly_partition_vertices; // addedFpPartitioningVerticesOnCorrespondingInputCutMesh
uint32_t internal_sourcemesh_vertex_count; // init from source_hmesh.number_of_vertices()
uint32_t client_sourcemesh_vertex_count; // init from numSrcMeshVertices
uint32_t internal_sourcemesh_face_count; // init from source_hmesh.number_of_faces()
uint32_t client_sourcemesh_face_count; // init from source_hmesh_face_count OR numSrcMeshFaces
// Stores the contiguous array of unsigned integers that define
// a triangulation of all [non-triangle faces] of the connected component.
// This vector is only populated if client invokes mcGetConnectedComponnentData
// with flag MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION and has the effect of
// triangulating every non-triangle face in the connected component.
std::vector<uint32_t> cdt_index_cache;
bool cdt_index_cache_initialized = false;
// stores the mapping between a CDT triangle in the connected component and
// the original "birth-face" in an input mesh (source mesh or cut mesh)
std::vector<uint32_t> cdt_face_map_cache;
bool cdt_face_map_cache_initialized = false;
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
// Stores the number of vertices per face of CC. This is an optimization
// because there is a possibility that face-sizes may (at-minimum) be queried
// twice by user. The first case is during the populating (i.e. second) call to the API
// mcGetConnectedComponentData(..., MC_CONNECTED_COMPONENT_DATA_FACE_SIZE, ...);
// The second case is during the populating (i.e. second) call to the API
// mcGetConnectedComponentData(..., MC_CONNECTED_COMPONENT_DATA_FACE, ...);.
// The key detail here is that the second case requires knowledge of the
// number of vertices in each face in order to know how to schedule parallel
// work with prefix-sums etc.. Thus, the optimization is useful only if
// building MCUT with multi-threading
std::vector<uint32_t> face_sizes_cache;
bool face_sizes_cache_initialized = false;
// see documentation of face_sizes_cache above
// Similar concepts but applied to MC_CONNECTED_COMPONENT_DATA_FACE_ADJACENT_FACE
std::vector<uint32_t> face_adjacent_faces_size_cache;
bool face_adjacent_faces_size_cache_initialized = false;
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
// non-zero if origin source and cut-mesh where perturbed
vec3 perturbation_vector = vec3(0.0);
};
// struct representing a fragment
struct fragment_cc_t : public connected_component_t {
McFragmentLocation fragmentLocation = (McFragmentLocation)0;
McFragmentSealType srcMeshSealType = (McFragmentSealType)0;
McPatchLocation patchLocation = (McPatchLocation)0;
};
// struct representing a patch
struct patch_cc_t : public connected_component_t {
McPatchLocation patchLocation = (McPatchLocation)0;
};
// struct representing a seam
struct seam_cc_t : public connected_component_t {
McSeamOrigin origin = (McSeamOrigin)0;
};
// struct representing an input (user provided mesh)
struct input_cc_t : public connected_component_t {
McInputOrigin origin = (McInputOrigin)0;
};
struct event_t {
std::future<void> m_future; // used to wait on event
// used to synchronise access to variables associated with the callback
// function.
// This also also allows us to overcome the edgecase that mcSetEventCallback
// is called after the task associated with an event has been completed,
// in which case the new callback will be invoked immediately.
// see "set_callback_data()" below
std::mutex m_callback_mutex;
struct {
// optional user callback, which is invoked when associated task is finished
pfn_McEvent_CALLBACK m_fn_ptr;
// pointer passed to user provided callback function
McVoid* m_data_ptr;
// atomic boolean flag indicating whether the callback associated with event
// object has been called
std::atomic<bool> m_invoked;
} m_callback_info;
// atomic boolean flag indicating whether the task associated with event
// object has completed running
std::atomic<bool> m_finished;
McEvent m_user_handle; // handle used by client app to reference this event object
// the Manager thread which was assigned the task of managing the task associated with this event object.
std::atomic<uint32_t> m_responsible_thread_id;
std::atomic<int32_t> m_runtime_exec_status; // API return code associated with respective task (for user to query)
std::atomic<size_t> m_timestamp_submit;
std::atomic<size_t> m_timestamp_start;
std::atomic<size_t> m_timestamp_end;
std::atomic<uint32_t> m_command_exec_status;
bool m_profiling_enabled;
McCommandType m_command_type;
// A callable object that also holds an std::future. Its purpose is to emulate
// the internal representation of an API task, where this time the task is actually
// a user command since this pointer is define ONLY for user events.
// The std::future object of the packaged task is used to initialize m_future
// when this event is a user event. This our internal mechanism allowing for
// API command to be able to effectively wait on user events.
std::unique_ptr<std::packaged_task<void()>> m_user_API_command_task_emulator;
McContext m_context;
const char* get_cmd_type_str()
{
switch (m_command_type) {
case McCommandType::MC_COMMAND_DISPATCH: {
return "MC_COMMAND_DISPATCH";
} break;
case McCommandType::MC_COMMAND_GET_CONNECTED_COMPONENT_DATA: {
return "MC_COMMAND_GET_CONNECTED_COMPONENT_DATA";
} break;
case McCommandType::MC_COMMAND_GET_CONNECTED_COMPONENTS: {
return "MC_COMMAND_GET_CONNECTED_COMPONENTS";
} break;
case McCommandType::MC_COMMAND_USER: {
return "MC_COMMAND_USER";
} break;
case McCommandType::MC_COMMAND_UKNOWN: {
return "MC_COMMAND_UKNOWN";
} break;
default:
fprintf(stderr, "unknown command type value (%d)\n", (int)m_command_type);
return "UNKNOWN VALUE";
}
}
explicit event_t(McEvent user_handle, McCommandType command_type)
: m_user_handle(user_handle)
, m_responsible_thread_id(UINT32_MAX)
, m_runtime_exec_status(MC_NO_ERROR)
, m_timestamp_submit(0)
, m_timestamp_start(0)
, m_timestamp_end(0)
, m_command_exec_status(MC_RESULT_MAX_ENUM)
, m_profiling_enabled(true)
, m_command_type(command_type)
, m_user_API_command_task_emulator(nullptr)
, m_context(nullptr)
{
log_msg("[MCUT] Create event (type=" << get_cmd_type_str() <<", handle=" << m_user_handle << ")");
m_callback_info.m_fn_ptr = nullptr;
m_callback_info.m_data_ptr = nullptr;
m_finished.store(false);
m_callback_info.m_invoked.store(true); // so that we do not call a null pointer/needless invoke the callback in the destructor
}
~event_t()
{
if (m_callback_info.m_invoked.load() == false && m_callback_info.m_fn_ptr != nullptr && m_runtime_exec_status.load() == MC_NO_ERROR) {
MCUT_ASSERT(m_user_handle != MC_NULL_HANDLE);
(*(m_callback_info.m_fn_ptr))(m_user_handle, m_callback_info.m_data_ptr);
}
log_msg("[MCUT] Destroy event (type=" << get_cmd_type_str() << ", handle=" << m_user_handle << ")");
}
inline std::size_t get_time_since_epoch()
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
inline void log_submit_time()
{
if (m_profiling_enabled) {
this->m_timestamp_submit.store(get_time_since_epoch());
}
// TODO: use specific acquire-release semantics
// see e.g.: https://stackoverflow.com/questions/13632344/understanding-c11-memory-fences
m_command_exec_status.store(McEventCommandExecStatus::MC_SUBMITTED);
}
inline void log_start_time()
{
if (m_profiling_enabled) {
this->m_timestamp_start.store(get_time_since_epoch());
}
m_command_exec_status = McEventCommandExecStatus::MC_RUNNING;
}
// logs time and sets m_command_exec_status to MC_COMPLETE
inline void log_end_time()
{
if (m_profiling_enabled) {
this->m_timestamp_end.store(get_time_since_epoch());
}
m_command_exec_status.store(McEventCommandExecStatus::MC_COMPLETE);
}
// thread-safe function to set the callback function for an event object
void set_callback_data(McEvent handle, pfn_McEvent_CALLBACK fn_ptr, McVoid* data_ptr)
{
std::lock_guard<std::mutex> lock(m_callback_mutex); // exclusive access
m_user_handle = handle;
m_callback_info.m_fn_ptr = fn_ptr;
m_callback_info.m_data_ptr = data_ptr;
m_callback_info.m_invoked.store(false);
if (m_finished.load() == true) { // see mutex documentation
// immediately invoke the callback
(*(m_callback_info.m_fn_ptr))(m_user_handle, m_callback_info.m_data_ptr);
m_callback_info.m_invoked.store(true);
}
}
// update the status of the event object to "finished"
void notify_task_complete(McResult exec_status)
{
m_finished = true;
m_runtime_exec_status = exec_status;
std::lock_guard<std::mutex> lock(m_callback_mutex);
if (m_callback_info.m_invoked.load() == false && m_callback_info.m_fn_ptr != nullptr) {
MCUT_ASSERT(m_user_handle != MC_NULL_HANDLE);
(*(m_callback_info.m_fn_ptr))(m_user_handle, m_callback_info.m_data_ptr);
m_callback_info.m_invoked.store(true);
}
}
};
// init in frontened.cpp
extern threadsafe_list<std::shared_ptr<event_t>> g_events;
extern std::atomic<std::uintptr_t> g_objects_counter; // a counter that is used to assign a unique value to a McObject handle that will be returned to the user
extern std::once_flag g_objects_counter_init_flag;
// our custome deleter function for std::unique_ptr variable of an array type
template <typename Derived>
void fn_delete_cc(connected_component_t* p)
{
log_msg("[MCUT] Destroy connected component " << p->m_user_handle);
delete dynamic_cast<Derived*>(p);
}
// struct defining the state of a context object
struct context_t {
private:
std::atomic<bool> m_done;
std::vector<thread_safe_queue<function_wrapper>> m_queues;
// Master/Manager thread(s) which are responsible for running the API calls
// When a user of MCUT calls one of the APIs (e.g. mcEnqueueDispatch) the task
// of actually executing everything related to that task will be handled by
// one Manager thread. This manager thread itself will be involved in
// computing some/all part of the respective task (think of it as the "main"
// thread insofar as the API task is concerned). Some API tasks contain code
// sections that run in parallel, which is where the Manager thread will also
// submit tasks to the shared compute threadpool ("m_compute_threadpool").
// NOTE: must be declared after "thread_pool_terminate" and "work_queues"
std::vector<std::thread> m_api_threads;
// join_threads m_joiner;
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
// A pool of threads that is shared by manager threads to execute e.g. parallel
// section of MCUT API tasks (see e.g. frontend.cpp and kernel.cpp)
std::unique_ptr<thread_pool> m_compute_threadpool;
#endif
// The state and flag variable current used to configure the next dispatch call
McFlags m_flags = (McFlags)0;
std::atomic<McDouble> m_general_position_enforcement_constant;
std::atomic<McUint32> m_max_num_perturbation_attempts;
std::atomic<McConnectedComponentFaceWindingOrder> m_connected_component_winding_order;
void api_thread_main(uint32_t thread_id)
{
log_msg("[MCUT] Launch API thread " << std::this_thread::get_id() << " (" << thread_id << ")");
do {
function_wrapper task;
// We try_pop() first in case the task "producer" (API) thread
// already invoked cond_var.notify_one() of "m_queues[thread_id]""
// BEFORE current thread first-entered this function.
if (!m_queues[thread_id].try_pop(task)) {
m_queues[thread_id].wait_and_pop(task);
}
if (m_done) {
break;
}
task();
} while (true);
log_msg("[MCUT] Shutdown API thread " << std::this_thread::get_id() << " (" << thread_id << ")");
}
public:
context_t(McContext handle, McFlags flags
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
,
uint32_t num_compute_threads
#endif
)
: m_done(false)
// , m_joiner(m_api_threads)
, m_flags(flags)
, m_general_position_enforcement_constant(1e-4)
, m_max_num_perturbation_attempts(1 << 2),
m_connected_component_winding_order(McConnectedComponentFaceWindingOrder::MC_CONNECTED_COMPONENT_FACE_WINDING_ORDER_AS_GIVEN)
, m_user_handle(handle)
, dbgCallbackBitfieldSource(0)
, dbgCallbackBitfieldType(0)
, dbgCallbackBitfieldSeverity(0)
{
log_msg("\n[MCUT] Create context " << m_user_handle);
try {
const uint32_t manager_thread_count = (flags & MC_OUT_OF_ORDER_EXEC_MODE_ENABLE) ? 2 : 1;
m_queues = std::vector<thread_safe_queue<function_wrapper>>(manager_thread_count);
for (uint32_t i = 0; i < manager_thread_count; ++i) {
m_queues[i].set_done_ptr(&m_done);
m_api_threads.push_back(std::thread(&context_t::api_thread_main, this, i));
}
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
// create the pool of compute threads. These are the worker threads that
// can be tasked with work from any manager-thread. Thus, manager threads
// share the available/user-specified compute threads.
m_compute_threadpool = std::unique_ptr<thread_pool>(new thread_pool(num_compute_threads, manager_thread_count));
#endif
} catch (...) {
shutdown();
log_msg("[MCUT] Destroy context due to exception" << m_user_handle);
throw;
}
}
~context_t()
{
shutdown();
log_msg("[MCUT] Destroy context " << m_user_handle);
}
void shutdown()
{
m_done = true;
std::atomic_thread_fence(std::memory_order_acq_rel);
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
m_compute_threadpool.reset();
#endif
for (int i = (int)m_api_threads.size() - 1; i >= 0; --i) {
m_queues[i].disrupt_wait_for_data();
if (m_api_threads[i].joinable()) {
m_api_threads[i].join();
}
}
}
McContext m_user_handle;
// returns the flags which determine the runtime configuration of this context
const McFlags& get_flags() const
{
return this->m_flags;
}
// returns (user controllable) epsilon representing the maximum by which the cut-mesh
// can be perturbed on any axis
McDouble get_general_position_enforcement_constant() const
{
return this->m_general_position_enforcement_constant.load(std::memory_order_acquire);
}
void set_general_position_enforcement_constant(McDouble new_value)
{
this->m_general_position_enforcement_constant.store(new_value, std::memory_order_release);
}
// returns (user controllable) maximum number of times by which the cut-mesh
// can be perturbed before giving (input likely need to be preprocessed)
McUint32 get_general_position_enforcement_attempts() const
{
return this->m_max_num_perturbation_attempts.load(std::memory_order_acquire);
}
void set_general_position_enforcement_attempts(McUint32 new_value)
{
this->m_max_num_perturbation_attempts.store(new_value, std::memory_order_release);
}
McConnectedComponentFaceWindingOrder get_connected_component_winding_order() const
{
return this->m_connected_component_winding_order.load(std::memory_order_acquire);
}
void set_connected_component_winding_order(McConnectedComponentFaceWindingOrder new_value)
{
this->m_connected_component_winding_order.store(new_value, std::memory_order_release);
}
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
thread_pool& get_shared_compute_threadpool()
{
return m_compute_threadpool.get()[0];
}
#endif
template <typename FunctionType>
McEvent prepare_and_submit_API_task(McCommandType cmdType, uint32_t numEventsInWaitlist, const McEvent* pEventWaitList, FunctionType api_fn)
{
//
// create the event object associated with the enqueued task
//
std::shared_ptr<event_t> event_ptr = std::shared_ptr<event_t>(new event_t(
reinterpret_cast<McEvent>(g_objects_counter.fetch_add(1, std::memory_order_relaxed)),
cmdType));
MCUT_ASSERT(event_ptr != nullptr);
g_events.push_front(event_ptr);
//event_ptr->m_user_handle = reinterpret_cast<McEvent>(g_objects_counter.fetch_add(1, std::memory_order_relaxed));
event_ptr->m_profiling_enabled = (this->m_flags & MC_PROFILING_ENABLE) != 0;
// event_ptr->m_command_type = cmdType;
event_ptr->log_submit_time();
// List of events the enqueued task depends on
//
// local copy that will be captured by-value (user permitted to re-use pEventWaitList)
const std::vector<McEvent> event_waitlist(pEventWaitList, pEventWaitList + numEventsInWaitlist);
//
// Determine which manager thread to assign the task
//
// the id of manager thread that will be assigned the current task
uint32_t responsible_thread_id = UINT32_MAX;
for (std::vector<McEvent>::const_iterator waitlist_iter = event_waitlist.cbegin(); waitlist_iter != event_waitlist.cend(); ++waitlist_iter) {
const McEvent& parent_task_event_handle = *waitlist_iter;
const std::shared_ptr<event_t> parent_task_event_ptr = g_events.find_first_if([=](std::shared_ptr<event_t> e) { return e->m_user_handle == parent_task_event_handle; });
if (parent_task_event_ptr == nullptr) {
throw std::invalid_argument("invalid event in waitlist");
}
const bool parent_task_is_not_finished = parent_task_event_ptr->m_finished.load() == false;
if (parent_task_is_not_finished && parent_task_event_ptr->m_command_type != McCommandType::MC_COMMAND_USER) {
// id of manager thread, which was assigned the parent task
responsible_thread_id = parent_task_event_ptr->m_responsible_thread_id.load();
MCUT_ASSERT(responsible_thread_id != UINT32_MAX);
MCUT_ASSERT(responsible_thread_id < (uint32_t)m_api_threads.size());
break;
}
}
const bool have_responsible_thread = responsible_thread_id != UINT32_MAX;
if (!have_responsible_thread) {
uint32_t thread_with_empty_queue = UINT32_MAX;
for (uint32_t i = 0; i < (uint32_t)m_api_threads.size(); ++i) {
if (m_queues[(i + 1) % (uint32_t)m_api_threads.size()].empty() == true) {
thread_with_empty_queue = i;
break;
}
}
if (thread_with_empty_queue != UINT32_MAX) {
responsible_thread_id = thread_with_empty_queue;
} else { // all threads have work to do
responsible_thread_id = 0; // just pick thread 0
}
}
//
// Package-up the task as a synchronised operation that will wait for
// other tasks in the event_waitlist, compute the operation, and finally update
// the respective event state with the completion status.
//
std::weak_ptr<event_t> event_weak_ptr(event_ptr);
std::packaged_task<void()> task(
[=]() {
McResult runtime_status_from_all_preceding_events = McResult::MC_NO_ERROR;
if (!event_waitlist.empty()) {
wait_for_events_impl((uint32_t)event_waitlist.size(), &event_waitlist[0], runtime_status_from_all_preceding_events); // block until events are done
}
// if any previous event failed then we cannot proceed with this task.
// i.e. no-Op
if (runtime_status_from_all_preceding_events != McResult::MC_NO_ERROR) {
return;
}
MCUT_ASSERT(!event_weak_ptr.expired());
{
std::shared_ptr<event_t> event = event_weak_ptr.lock();
MCUT_ASSERT(event != nullptr);
{
McResult return_value = McResult::MC_NO_ERROR;
per_thread_api_log_str.clear();
event->log_start_time();
try {
api_fn(); // execute the API function.
}
CATCH_POSSIBLE_EXCEPTIONS(per_thread_api_log_str); // exceptions may be thrown due to runtime errors, which must be reported back to user
if (!per_thread_api_log_str.empty()) {
std::fprintf(stderr, "%s(...) -> %s (EventID=%p)\n", __FUNCTION__, per_thread_api_log_str.c_str(), event == nullptr ? (McEvent)0 : event->m_user_handle);
if (return_value == McResult::MC_NO_ERROR) // i.e. problem with basic local parameter checks
{
return_value = McResult::MC_INVALID_VALUE;
}
}
event->notify_task_complete(return_value); // updated event state to indicate task completion (lock-based)
event->log_end_time();
}
}
});
event_ptr->m_future = task.get_future(); // the future we can later wait on via mcWaitForEVents
event_ptr->m_responsible_thread_id = responsible_thread_id;
m_queues[responsible_thread_id].push(std::move(task)); // enqueue task to be executed when responsible API thread is free
return event_ptr->m_user_handle;
}
// the current set of connected components associated with context
threadsafe_list<std::shared_ptr<connected_component_t>> connected_components;
// McFlags dispatchFlags = (McFlags)0;
// client/user debugging variables
// ------------------------------
// function pointer to user-define callback function for status/erro reporting
pfn_mcDebugOutput_CALLBACK debugCallback = nullptr;
// user provided data for callback
const McVoid* debugCallbackUserParam = nullptr;
std::mutex debugCallbackMutex;
// controller for permmited messages based on the source of message
std::atomic<McFlags> dbgCallbackBitfieldSource;
// controller for permmited messages based on the type of message
std::atomic<McFlags> dbgCallbackBitfieldType;
// controller for permmited messages based on the severity of message
std::atomic<McFlags> dbgCallbackBitfieldSeverity;
bool dbgCallbackAllowAsyncCalls = true;
void set_debug_callback_data(pfn_mcDebugOutput_CALLBACK cb, const McVoid* data_ptr)
{
std::lock_guard<std::mutex> lguard(debugCallbackMutex);
debugCallback = cb;
debugCallbackUserParam = data_ptr;
}
struct debug_log_msg_t {
McDebugSource source;
McDebugType type;
McDebugSeverity severity;
std::string str;
};
std::vector<debug_log_msg_t> m_debug_logs;
// function to invoke the user-provided debug call back
void dbg_cb(McDebugSource source,
McDebugType type,
unsigned int id, // unused
McDebugSeverity severity,
const std::string& message)
{
if (this->m_flags & McContextCreationFlags::MC_DEBUG) // information logged only during debug mode
{
std::unique_lock<std::mutex> ulock(debugCallbackMutex, std::defer_lock);
if (!dbgCallbackAllowAsyncCalls) {
ulock.lock();
} // otherwise the callback will be invoked asynchronously
// can we log this type of message? (based on user preferences via mcDebugMessageControl)
const bool canLog = ((uint32_t)source & dbgCallbackBitfieldSource.load(std::memory_order_acquire)) && //
((uint32_t)type & dbgCallbackBitfieldType.load(std::memory_order_acquire)) && //
((uint32_t)severity & dbgCallbackBitfieldSeverity.load(std::memory_order_acquire));
if (canLog) {
if (debugCallback != nullptr) { // user gave us a callback function pointer
(*debugCallback)(source, type, id, severity, message.length(), message.c_str(), debugCallbackUserParam);
} else // write to the internal log
{
m_debug_logs.emplace_back(debug_log_msg_t());
debug_log_msg_t& dbg_log = m_debug_logs.back();
dbg_log.source = source;
dbg_log.type = type;
dbg_log.severity = severity;
dbg_log.str = message;
}
}
}
}
};
// list of contexts created by client/user
extern "C" threadsafe_list<std::shared_ptr<context_t>> g_contexts;
#endif // #ifndef _FRONTEND_H_