Create mjpDecoder, mj_parse, and mju_decodeResource
- plugin system similar to mjpResourceProvider, but instead of loading a resource it converts an existing mjResource into an mjSpec. - The returned spec is then composed into the referencing spec. - This enables different file types to generate arbitrary specs, and allows us to separate format parsing from compilation code. Follow up CLs will move some of the logic in src/engine for PNG, USD, KTX, OBJ loading into decoders. The mj_parse function MjSpec from a given file, it's a more generic version of mj_parseXML. In it's implementation, mj_parse as opposed to mj_parseXML will look for any registered decoder and not assume we are striclty dealing with MJCF. PiperOrigin-RevId: 826149497 Change-Id: I0ece26904280cb94bd5ded6dd5a565c539d60254
This commit is contained in:
committed by
Copybara-Service
parent
f2badc05ac
commit
57f7145806
@@ -25,6 +25,7 @@
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
@@ -62,6 +63,16 @@ int strklen(const char* s) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// return filename extension
|
||||
std::string getext(std::string_view filename) {
|
||||
size_t dot = filename.find_last_of('.');
|
||||
|
||||
if (dot == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
return std::string(filename.substr(dot, filename.size() - dot));
|
||||
}
|
||||
|
||||
// copy a null-terminated string into a new heap-allocated char array managed by a unique_ptr
|
||||
std::unique_ptr<char[]> CopyName(const char* s) {
|
||||
int len = strklen(s);
|
||||
@@ -265,6 +276,77 @@ bool GlobalTable<mjpResourceProvider>::CopyObject(mjpResourceProvider& dst, cons
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* GlobalTable<mjpDecoder>::HumanReadableTypeName() {
|
||||
return "resource decoder";
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string_view GlobalTable<mjpDecoder>::ObjectKey(const mjpDecoder& decoder) {
|
||||
// When registering decoders, if the user provides both a content type and an extension we add two
|
||||
// entries to the table. One with content_type set and extension unset, and one with the opposite.
|
||||
// This means that within a vector, we will only ever have either content_type or extension.
|
||||
if (decoder.content_type) {
|
||||
if (int len = strklen(decoder.content_type); len != -1) {
|
||||
return std::string_view(decoder.content_type, len);
|
||||
}
|
||||
}
|
||||
return std::string_view(decoder.extension, strklen(decoder.extension));
|
||||
}
|
||||
|
||||
// return true if two resource providers are identical
|
||||
template <>
|
||||
bool GlobalTable<mjpDecoder>::ObjectEqual(const mjpDecoder& d1, const mjpDecoder& d2) {
|
||||
// check if two resource providers are identical
|
||||
if (!(CaseInsensitiveEqual(d1.content_type, d2.content_type) &&
|
||||
CaseInsensitiveEqual(d1.extension, d2.extension) &&
|
||||
d1.decode == d2.decode &&
|
||||
d1.can_decode == d2.can_decode)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool GlobalTable<mjpDecoder>::CopyObject(mjpDecoder& dst, const mjpDecoder& src, ErrorMessage& err) {
|
||||
// Just a list of pointers so copy directly.
|
||||
dst = src;
|
||||
dst.content_type = nullptr;
|
||||
dst.extension = nullptr;
|
||||
|
||||
if (src.content_type) {
|
||||
std::unique_ptr<char[]> content_type = CopyName(src.content_type);
|
||||
if (!content_type) {
|
||||
if (strklen(src.content_type) == -1) {
|
||||
std::snprintf(err, sizeof(err),
|
||||
"decoder->content_type length exceeds the maximum limit of %d", kMaxNameLength);
|
||||
} else {
|
||||
std::snprintf(err, sizeof(err), "failed to allocate memory for decoder content_type");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
dst.content_type = content_type.release();
|
||||
}
|
||||
|
||||
if (src.extension) {
|
||||
std::unique_ptr<char[]> extension = CopyName(src.extension);
|
||||
if (!extension) {
|
||||
if (strklen(src.extension) == -1) {
|
||||
std::snprintf(err, sizeof(err),
|
||||
"decoder->extension length exceeds the maximum limit of %d", kMaxNameLength);
|
||||
} else {
|
||||
std::snprintf(err, sizeof(err), "failed to allocate memory for decoder extension");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
dst.extension = extension.release();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// globally register a plugin (thread-safe), return new slot id
|
||||
int mjp_registerPlugin(const mjpPlugin* plugin) {
|
||||
if (!plugin->name) {
|
||||
@@ -382,6 +464,71 @@ const mjpResourceProvider* mjp_getResourceProviderAtSlot(int slot) {
|
||||
return GlobalTable<mjpResourceProvider>::GetSingleton().GetAtSlot(slot - 1);
|
||||
}
|
||||
|
||||
// register a resource decoder
|
||||
void mjp_registerDecoder(const mjpDecoder* decoder) {
|
||||
if (!decoder->decode || !decoder->can_decode) {
|
||||
mju_warning("decoder must provide decode and can_decode callbacks.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!decoder->content_type && !decoder->extension) {
|
||||
mju_warning("decoder must provide content_type and/or extensions.");
|
||||
return;
|
||||
}
|
||||
|
||||
mjpDecoder decoder_copy = *decoder;
|
||||
|
||||
// Register with content_type
|
||||
if (decoder->content_type) {
|
||||
decoder_copy.extension = nullptr;
|
||||
GlobalTable<mjpDecoder>::GetSingleton().AppendIfUnique(decoder_copy);
|
||||
}
|
||||
|
||||
// Register with extensions
|
||||
if (decoder->extension) {
|
||||
decoder_copy.content_type = nullptr;
|
||||
std::string extensions_str(decoder->extension);
|
||||
std::stringstream ss(extensions_str);
|
||||
std::string extension;
|
||||
while (std::getline(ss, extension, '|')) {
|
||||
if (!extension.empty()) {
|
||||
decoder_copy.extension = extension.c_str();
|
||||
GlobalTable<mjpDecoder>::GetSingleton().AppendIfUnique(decoder_copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set default resource decoder definition
|
||||
void mjp_defaultDecoder(mjpDecoder* decoder) {
|
||||
std::memset(decoder, 0, sizeof(*decoder));
|
||||
}
|
||||
|
||||
// find a decoder that can process a given resource and content_type
|
||||
const mjpDecoder* mjp_findDecoder(const mjResource* resource, const char* content_type) {
|
||||
auto extension = getext(resource->name);
|
||||
if (strklen(content_type) == -1 && extension.empty()) {
|
||||
mju_warning("Must provide extension or content_type to mjp_findDecoder.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (strklen(content_type) > 0) {
|
||||
auto* decoder = GlobalTable<mjpDecoder>::GetSingleton().GetByKey(content_type, nullptr);
|
||||
if (decoder && decoder->can_decode(resource)) {
|
||||
return decoder;
|
||||
}
|
||||
}
|
||||
|
||||
if (!extension.empty()) {
|
||||
auto* decoder = GlobalTable<mjpDecoder>::GetSingleton().GetByKey(extension.c_str(), nullptr);
|
||||
if (decoder && decoder->can_decode(resource)) {
|
||||
return decoder;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// load plugins from a dynamic library
|
||||
void mj_loadPluginLibrary(const char* path) {
|
||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||
|
||||
Reference in New Issue
Block a user