carb/BindingsUtils.h
File members: carb/BindingsUtils.h
// Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once
#include "ClientUtils.h"
#include "Defines.h"
#include "Format.h"
#include "Framework.h"
#include "InterfaceUtils.h"
#include "ObjectUtils.h"
#include "assert/AssertUtils.h"
#include "logging/Log.h"
#include "profiler/Profile.h"
#include <functional>
#include <sstream>
#include <string>
#include <unordered_map>
namespace carb
{
template <typename InterfaceType, typename ReturnType, typename... Args>
auto wrapInterfaceFunction(ReturnType (*InterfaceType::*p)(Args...))
-> std::function<ReturnType(InterfaceType&, Args...)>
{
return [p](InterfaceType& c, Args... args) { return (c.*p)(args...); };
}
template <typename InterfaceType, typename ReturnType, typename... Args>
auto wrapInterfaceFunction(const InterfaceType* c, ReturnType (*InterfaceType::*p)(Args...))
-> std::function<ReturnType(Args...)>
{
return [c, p](Args... args) { return (c->*p)(args...); };
}
template <typename InterfaceType>
InterfaceType* acquireInterfaceForBindings(const char* pluginName = nullptr)
{
carb::Framework* framework = carb::getFramework();
InterfaceType* iface = framework->tryAcquireInterface<InterfaceType>(pluginName);
if (!iface)
{
// Try load plugins with default desc (all of them)
carb::PluginLoadingDesc desc = carb::PluginLoadingDesc::getDefault();
framework->loadPlugins(desc);
iface = framework->tryAcquireInterface<InterfaceType>(pluginName);
if (!iface)
{
// somehow this header gets picked up by code compiled by -fno-exceptions
#if !CARB_EXCEPTIONS_ENABLED
OMNI_FATAL_UNLESS(iface, "Failed to acquire interface: '%s' (pluginName: '%s')",
InterfaceType::getInterfaceDesc().name, pluginName ? pluginName : "nullptr");
#else
throw std::runtime_error(fmt::format("Failed to acquire interface: {} (pluginName: {})",
InterfaceType::getInterfaceDesc().name,
pluginName ? pluginName : "nullptr"));
#endif
}
}
return iface;
}
template <typename InterfaceType>
InterfaceType* getCachedInterfaceForBindings()
{
InterfaceType* iface = carb::getCachedInterface<InterfaceType>();
CARB_UNLIKELY_IF(!iface)
{
// somehow this header gets picked up by code compiled by -fno-exceptions
#if !CARB_EXCEPTIONS_ENABLED
OMNI_FATAL_UNLESS(iface, "Failed to acquire cached interface: '%s'", InterfaceType::getInterfaceDesc().name);
#else
throw std::runtime_error(
fmt::format("Failed to acquire cached interface: {}", InterfaceType::getInterfaceDesc().name));
#endif
}
return iface;
}
template <typename InterfaceType>
InterfaceType* acquireInterfaceFromLibraryForBindings(const char* libraryPath)
{
carb::Framework* framework = carb::getFramework();
InterfaceType* iface = framework->tryAcquireInterfaceFromLibrary<InterfaceType>(libraryPath);
if (!iface)
{
// somehow this header gets picked up by code compiled by -fno-exceptions
#if !CARB_EXCEPTIONS_ENABLED
OMNI_FATAL_UNLESS(
"Failed to acquire interface: '%s' from: '%s')", InterfaceType::getInterfaceDesc().name, libraryPath);
#else
throw std::runtime_error(fmt::format(
"Failed to acquire interface: {} from: {})", InterfaceType::getInterfaceDesc().name, libraryPath));
#endif
}
return iface;
}
inline Framework* acquireFrameworkForBindings(const char* scriptLanguage)
{
// Acquire framework and set into global variable
// Is framework was previously invalid, we are the first who calling it and it will be created during acquire.
// Register builtin plugin in that case
const bool firstStart = !isFrameworkValid();
Framework* f = acquireFramework(g_carbClientName);
if (!f)
return nullptr;
g_carbFramework = f;
// Register as binding for the given script language
f->registerScriptBinding(BindingType::Binding, g_carbClientName, scriptLanguage);
// Starting up logging
if (firstStart)
detail::registerBuiltinLogging(f);
logging::registerLoggingForClient();
// Starting up filesystem and profiling
if (firstStart)
{
detail::registerBuiltinFileSystem(f);
detail::registerBuiltinAssert(f);
detail::registerBuiltinThreadUtil(f);
}
profiler::registerProfilerForClient();
assert::registerAssertForClient();
l10n::registerLocalizationForClient();
return f;
}
inline void releaseFrameworkForBindings()
{
if (isFrameworkValid())
{
profiler::deregisterProfilerForClient();
logging::deregisterLoggingForClient();
assert::deregisterAssertForClient();
l10n::deregisterLocalizationForClient();
// Leave g_carbFramework intact here since the framework itself remains valid; we are just signaling our end of
// using it. There may be some static destructors (i.e. CachedInterface) that still need to use it.
}
else
{
// The framework became invalid while we were loaded.
g_carbFramework = nullptr;
}
}
class FrameworkInitializerForBindings
{
public:
FrameworkInitializerForBindings(const char* scriptLanguage = "python")
{
acquireFrameworkForBindings(scriptLanguage);
m_thisModuleStartedOmniCore = !omniGetTypeFactoryWithoutAcquire();
if (m_thisModuleStartedOmniCore)
{
// at this point, the core should already be started by the omniverse host executable (i.e. app). however,
// if we're in the python native interpreter, it will not automatically startup the core. here we account
// for this situation by checking if the core is started, and if not, start it.
//
// OMNI_CORE_START internally reference counts the start/stop calls, so one would think we could always make
// this call (with a corresponding call to OMNI_CORE_STOP in the destructor).
//
// however, the Python interpreter doesn't like unloading .pyd files, meaning our destructor will not be
// called.
//
// this shouldn't be an issue, unless the host expects to be able to load, unload, and then reload the core.
// the result here would be the internal reference count would get confused, causing the core to never be
// unloaded.
//
// we don't expect apps to reload the core, but our unit tests do. so, here we only let python increment
// the ref count if we think its the first entity to start the core (i.e. running in the interpreter).
OMNI_CORE_START(nullptr);
}
omni::structuredlog::addModulesSchemas();
}
~FrameworkInitializerForBindings()
{
if (m_thisModuleStartedOmniCore)
{
OMNI_CORE_STOP_FOR_BINDINGS();
m_thisModuleStartedOmniCore = false;
}
releaseFrameworkForBindings();
}
bool m_thisModuleStartedOmniCore;
};
template <class T1, class T2>
inline size_t hashPair(T1 t1, T2 t2)
{
std::size_t res = 0;
using std::hash;
res = carb::hashCombine(res, hash<T1>{}(t1));
res = carb::hashCombine(res, hash<T2>{}(t2));
return res;
}
#ifndef DOXYGEN_SHOULD_SKIP_THIS
template <class KeyT, typename ReturnT, typename... Args>
class ScriptCallbackRegistry
{
public:
using FuncT = std::function<ReturnT(Args...)>;
static FuncT* create(const FuncT& f)
{
return new FuncT(f);
}
static void destroy(FuncT* f)
{
delete f;
}
void add(const KeyT& key, FuncT* ptr)
{
if (!m_map.insert({ key, ptr }).second)
{
CARB_LOG_ERROR("Scripting callback with that key already exists.");
}
}
bool tryRemoveAndDestroy(const KeyT& key)
{
auto it = m_map.find(key);
if (it != m_map.end())
{
destroy(it->second);
m_map.erase(it);
return true;
}
return false;
}
void removeAndDestroy(const KeyT& key)
{
if (!tryRemoveAndDestroy(key))
{
CARB_LOG_ERROR("Removing unknown scripting callback.");
}
}
private:
std::unordered_map<KeyT, FuncT*> m_map;
};
template <typename ClassT, typename ObjectT, typename... Args>
auto wrapInStealObject(ObjectT* (ClassT::*f)(Args...))
{
return [f](ClassT* c, Args... args) { return carb::stealObject<ObjectT>((c->*f)(args...)); };
}
#endif
} // namespace carb
#define CARB_BINDINGS(clientName, ...) \
CARB_GLOBALS(clientName) \
carb::FrameworkInitializerForBindings g_carbFrameworkInitializerForBindings{ __VA_ARGS__ };
#define CARB_BINDINGS_EX(clientName_, desc_, ...) \
CARB_GLOBALS_EX(clientName_, desc_) \
carb::FrameworkInitializerForBindings g_carbFrameworkInitializerForBindings{ __VA_ARGS__ };