| #include "rive/file.hpp" | |
| #include "rive/bindable_artboard.hpp" | |
| #include "rive/runtime_header.hpp" | |
| #include "rive/animation/animation.hpp" | |
| #include "rive/artboard_component_list.hpp" | |
| #include "rive/core/field_types/core_color_type.hpp" | |
| #include "rive/core/field_types/core_double_type.hpp" | |
| #include "rive/core/field_types/core_string_type.hpp" | |
| #include "rive/core/field_types/core_uint_type.hpp" | |
| #include "rive/generated/animation/listener_types/listener_input_type_semantic_base.hpp" | |
| #include "rive/generated/core_registry.hpp" | |
| #include "rive/animation/listener_types/listener_input_type_semantic.hpp" | |
| #include "rive/importers/artboard_importer.hpp" | |
| #include "rive/importers/backboard_importer.hpp" | |
| #include "rive/importers/bindable_property_importer.hpp" | |
| #include "rive/importers/data_converter_group_importer.hpp" | |
| #include "rive/importers/data_converter_formula_importer.hpp" | |
| #include "rive/importers/enum_importer.hpp" | |
| #include "rive/importers/file_asset_importer.hpp" | |
| #include "rive/importers/text_asset_importer.hpp" | |
| #include "rive/importers/import_stack.hpp" | |
| #ifdef WITH_RIVE_SCRIPTING | |
| #ifdef WITH_RIVE_SCRIPTING_LUAU | |
| #include "rive/lua/rive_lua_libs.hpp" | |
| #include "rive/lua/lua_state.hpp" | |
| #endif | |
| #endif | |
| #include "rive/importers/keyed_object_importer.hpp" | |
| #include "rive/importers/keyed_property_importer.hpp" | |
| #include "rive/importers/linear_animation_importer.hpp" | |
| #include "rive/importers/scripted_object_importer.hpp" | |
| #include "rive/importers/state_machine_importer.hpp" | |
| #include "rive/importers/state_machine_listener_importer.hpp" | |
| #include "rive/importers/state_machine_layer_importer.hpp" | |
| #include "rive/importers/layer_state_importer.hpp" | |
| #include "rive/importers/state_transition_importer.hpp" | |
| #include "rive/importers/state_machine_layer_component_importer.hpp" | |
| #include "rive/importers/transition_viewmodel_condition_importer.hpp" | |
| #include "rive/importers/listener_input_type_gamepad_importer.hpp" | |
| #include "rive/importers/listener_input_type_keyboard_importer.hpp" | |
| #include "rive/importers/listener_input_type_semantic_importer.hpp" | |
| #include "rive/importers/viewmodel_importer.hpp" | |
| #include "rive/importers/viewmodel_instance_importer.hpp" | |
| #include "rive/importers/viewmodel_instance_list_importer.hpp" | |
| #include "rive/importers/data_bind_path_importer.hpp" | |
| #include "rive/animation/blend_state_transition.hpp" | |
| #include "rive/animation/any_state.hpp" | |
| #include "rive/animation/entry_state.hpp" | |
| #include "rive/animation/exit_state.hpp" | |
| #include "rive/animation/animation_state.hpp" | |
| #include "rive/animation/blend_state_1d_input.hpp" | |
| #include "rive/animation/blend_state_1d_viewmodel.hpp" | |
| #include "rive/animation/blend_state_direct.hpp" | |
| #include "rive/animation/transition_property_viewmodel_comparator.hpp" | |
| #include "rive/constraints/scrolling/scroll_physics.hpp" | |
| #include "rive/data_bind/data_bind.hpp" | |
| #include "rive/data_bind/data_bind_path.hpp" | |
| #include "rive/data_bind/bindable_property.hpp" | |
| #include "rive/data_bind/bindable_property_artboard.hpp" | |
| #include "rive/data_bind/bindable_property_asset.hpp" | |
| #include "rive/data_bind/bindable_property_viewmodel.hpp" | |
| #include "rive/data_bind/bindable_property_number.hpp" | |
| #include "rive/data_bind/bindable_property_string.hpp" | |
| #include "rive/data_bind/bindable_property_color.hpp" | |
| #include "rive/data_bind/bindable_property_enum.hpp" | |
| #include "rive/data_bind/bindable_property_integer.hpp" | |
| #include "rive/data_bind/bindable_property_boolean.hpp" | |
| #include "rive/data_bind/bindable_property_trigger.hpp" | |
| #include "rive/data_bind/bindable_property_asset.hpp" | |
| #include "rive/data_bind/converters/data_converter_group.hpp" | |
| #include "rive/data_bind/converters/data_converter_number_to_list.hpp" | |
| #include "rive/assets/file_asset.hpp" | |
| #include "rive/assets/audio_asset.hpp" | |
| #include "rive/assets/blob_asset.hpp" | |
| #include "rive/assets/script_asset.hpp" | |
| #include "rive/assets/script_module_asset.hpp" | |
| #ifdef WITH_RIVE_SCRIPTING_WASM | |
| #include "rive/wasm/wasm_scripting_vm.hpp" | |
| #endif | |
| #include "rive/assets/shader_asset.hpp" | |
| #include "rive/assets/file_asset_contents.hpp" | |
| #include "rive/scripted/scripted_drawable.hpp" | |
| #include "rive/scripted/scripted_layout.hpp" | |
| #include "rive/scripted/scripted_object.hpp" | |
| #include "rive/scripted/scripted_path_effect.hpp" | |
| #include "rive/scripted/scripted_interpolator.hpp" | |
| #include "rive/viewmodel/viewmodel.hpp" | |
| #include "rive/viewmodel/data_enum.hpp" | |
| #include "rive/viewmodel/viewmodel_instance.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_list.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_viewmodel.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_number.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_string.hpp" | |
| #include "rive/viewmodel/viewmodel_property_viewmodel.hpp" | |
| #include "rive/viewmodel/viewmodel_property_string.hpp" | |
| #include "rive/viewmodel/viewmodel_property_number.hpp" | |
| #include "rive/viewmodel/viewmodel_property_enum.hpp" | |
| #include "rive/viewmodel/viewmodel_property_enum_custom.hpp" | |
| #include "rive/viewmodel/viewmodel_property_enum_system.hpp" | |
| #include "rive/viewmodel/viewmodel_property_list.hpp" | |
| #include "rive/viewmodel/viewmodel_property_trigger.hpp" | |
| #include "rive/viewmodel/viewmodel_property_symbol_list_index.hpp" | |
| #include "rive/viewmodel/runtime/viewmodel_runtime.hpp" | |
| #include "rive/view_model_type.hpp" | |
| // Default namespace for Rive Cpp code | |
| using namespace rive; | |
| #if defined(DEBUG) | |
| size_t File::debugTotalFileCount = 0; | |
| #endif | |
| #if !defined(RIVE_FMT_U64) | |
| #if defined(__ANDROID__) | |
| #if INTPTR_MAX == INT64_MAX | |
| #define RIVE_FMT_U64 "%lu" | |
| #define RIVE_FMT_I64 "%ld" | |
| #else | |
| #define RIVE_FMT_U64 "%llu" | |
| #define RIVE_FMT_I64 "%lld" | |
| #endif | |
| #elif defined(_WIN32) | |
| #define RIVE_FMT_U64 "%lld" | |
| #define RIVE_FMT_I64 "%llu" | |
| #else | |
| #include <inttypes.h> | |
| #define RIVE_FMT_U64 "%" PRIu64 | |
| #define RIVE_FMT_I64 "%" PRId64 | |
| #endif | |
| #endif | |
| // Import a single Rive runtime object. | |
| // Used by the file importer. | |
| #ifdef WITH_RIVE_SCRIPTING_WASM | |
| void File::adoptWasmScriptingVM(std::unique_ptr<WasmScriptingVM> vm) | |
| { | |
| m_wasmVMs.clear(); | |
| WasmScriptingVM* raw = vm.get(); | |
| m_wasmVMs.push_back(std::move(vm)); | |
| for (auto& asset : m_fileAssets) | |
| { | |
| if (asset->is<ScriptAsset>()) | |
| { | |
| asset->as<ScriptAsset>()->wasmBackend(raw); | |
| } | |
| } | |
| } | |
| bool File::applyWasmRegistration(const std::string& moduleName, int ref) | |
| { | |
| for (auto& asset : m_fileAssets) | |
| { | |
| if (!asset->is<ScriptAsset>()) | |
| { | |
| continue; | |
| } | |
| auto* script = asset->as<ScriptAsset>(); | |
| if (script->moduleName() == moduleName) | |
| { | |
| if (!script->isModule() && ref != 0) | |
| { | |
| script->registrationComplete(ref); | |
| } | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| #endif | |
| static Core* readRuntimeObject(BinaryReader& reader, | |
| const RuntimeHeader& header) | |
| { | |
| auto coreObjectKey = reader.readVarUintAs<int>(); | |
| auto object = CoreRegistry::makeCoreInstance(coreObjectKey); | |
| while (true) | |
| { | |
| auto propertyKey = reader.readVarUintAs<uint16_t>(); | |
| if (propertyKey == 0) | |
| { | |
| // Terminator. https://media.giphy.com/media/7TtvTUMm9mp20/giphy.gif | |
| break; | |
| } | |
| if (reader.hasError()) | |
| { | |
| delete object; | |
| return nullptr; | |
| } | |
| if (object == nullptr || !object->deserialize(propertyKey, reader)) | |
| { | |
| // We have an unknown object or property, first see if core knows | |
| // the property type. | |
| int id = CoreRegistry::propertyFieldId(propertyKey); | |
| if (id == -1) | |
| { | |
| // No, check if it's in toc. | |
| id = header.propertyFieldId(propertyKey); | |
| } | |
| if (id == -1) | |
| { | |
| // Still couldn't find it, give up. | |
| fprintf(stderr, | |
| "Unknown property key %d, missing from property ToC.\n", | |
| propertyKey); | |
| delete object; | |
| return nullptr; | |
| } | |
| switch (id) | |
| { | |
| case CoreUintType::id: | |
| // Uint64 shares the uint type id; skip the full range so | |
| // an unknown 64 bit value never aborts the read. | |
| reader.readVarUint64(); | |
| break; | |
| case CoreStringType::id: | |
| CoreStringType::deserialize(reader); | |
| break; | |
| case CoreDoubleType::id: | |
| CoreDoubleType::deserialize(reader); | |
| break; | |
| case CoreColorType::id: | |
| CoreColorType::deserialize(reader); | |
| break; | |
| } | |
| } | |
| } | |
| if (object == nullptr) | |
| { | |
| // fprintf(stderr, | |
| // "File contains an unknown object with coreType " RIVE_FMT_U64 | |
| // ", which " "this runtime doesn't understand.\n", | |
| // coreObjectKey); | |
| return nullptr; | |
| } | |
| #ifdef WITH_RIVE_EDITOR | |
| // The .riv importer is exclusively the runtime path (editor flow | |
| // hydrates via coop, never through here), so imported objects are | |
| // fully wired by the import stack — mark them validated or the | |
| // editor build's hasValidated() gate starves every generated | |
| // ${name}Changed() on runtime instances (frozen animation dirt, | |
| // silent VMI value callbacks). | |
| object->markValidated(); | |
| #endif | |
| return object; | |
| } | |
| bool File::deterministicMode = false; | |
| File::File(Factory* factory, rcp<FileAssetLoader> assetLoader) : | |
| m_factory(factory), m_assetLoader(std::move(assetLoader)) | |
| { | |
| #if defined(DEBUG) | |
| debugTotalFileCount++; | |
| #endif | |
| assert(factory); | |
| } | |
| File::~File() | |
| { | |
| #if defined(DEBUG) | |
| debugTotalFileCount--; | |
| #endif | |
| #ifdef WITH_RIVE_SCRIPTING_LUAU | |
| cleanupScriptingVM(); | |
| #endif | |
| for (auto artboard : m_artboards) | |
| { | |
| delete artboard; | |
| } | |
| for (auto& viewModel : m_ViewModels) | |
| { | |
| viewModel->unref(); | |
| } | |
| #ifdef WITH_RIVE_TOOLS | |
| m_viewModelInstanceRegistrar = nullptr; | |
| #endif | |
| for (auto& enumData : m_Enums) | |
| { | |
| enumData->unref(); | |
| } | |
| for (auto& dataConverter : m_DataConverters) | |
| { | |
| delete dataConverter; | |
| } | |
| for (auto& keyframeInterpolator : m_keyframeInterpolators) | |
| { | |
| delete keyframeInterpolator; | |
| } | |
| for (auto& physics : m_scrollPhysics) | |
| { | |
| delete physics; | |
| } | |
| delete m_backboard; | |
| } | |
| rcp<File> File::import(Span<const uint8_t> bytes, | |
| Factory* factory, | |
| ImportResult* result, | |
| rcp<FileAssetLoader> assetLoader, | |
| ScriptingVM* vm) | |
| { | |
| BinaryReader reader(bytes); | |
| RuntimeHeader header; | |
| if (!RuntimeHeader::read(reader, header)) | |
| { | |
| fprintf(stderr, "Bad header\n"); | |
| if (result) | |
| { | |
| *result = ImportResult::malformed; | |
| } | |
| return nullptr; | |
| } | |
| if (header.majorVersion() != majorVersion) | |
| { | |
| fprintf(stderr, | |
| "Unsupported version %u.%u expected %u.%u.\n", | |
| header.majorVersion(), | |
| header.minorVersion(), | |
| majorVersion, | |
| minorVersion); | |
| if (result) | |
| { | |
| *result = ImportResult::unsupportedVersion; | |
| } | |
| return nullptr; | |
| } | |
| auto file = make_rcp<File>(factory, std::move(assetLoader)); | |
| #ifdef WITH_RIVE_SCRIPTING_LUAU | |
| if (vm != nullptr) | |
| { | |
| file->setScriptingVM(ref_rcp(vm)); | |
| } | |
| #else | |
| (void)vm; | |
| #endif | |
| auto readResult = file->read(reader, header); | |
| if (result) | |
| { | |
| *result = readResult; | |
| } | |
| if (readResult != ImportResult::success) | |
| { | |
| file.reset(nullptr); | |
| } | |
| return file; | |
| } | |
| ImportResult File::read(BinaryReader& reader, const RuntimeHeader& header) | |
| { | |
| ImportStack importStack; | |
| importStack.version(header.majorVersion(), header.minorVersion()); | |
| #ifdef WITH_RIVE_SCRIPTING | |
| std::vector<InBandContent> inBandContent; | |
| #endif | |
| // TODO: @hernan consider moving this to a special importer. It's not that | |
| // simple because Core doesn't have a typeKey, so it should be treated as | |
| // a special case. In any case, it's not that bad having it here for now. | |
| Core* lastBindableObject = nullptr; | |
| while (!reader.reachedEnd()) | |
| { | |
| auto object = readRuntimeObject(reader, header); | |
| if (object == nullptr) | |
| { | |
| importStack.readNullObject(); | |
| continue; | |
| } | |
| if (!object->is<DataBind>()) | |
| { | |
| lastBindableObject = object; | |
| } | |
| else if (lastBindableObject != nullptr) | |
| { | |
| object->as<DataBind>()->target(lastBindableObject); | |
| } | |
| if (object->import(importStack) == StatusCode::Ok) | |
| { | |
| switch (object->coreType()) | |
| { | |
| case Backboard::typeKey: | |
| m_backboard = object->as<Backboard>(); | |
| break; | |
| case Artboard::typeKey: | |
| { | |
| Artboard* ab = object->as<Artboard>(); | |
| ab->m_Factory = m_factory; | |
| m_artboards.push_back(ab); | |
| } | |
| break; | |
| case ImageAsset::typeKey: | |
| case FontAsset::typeKey: | |
| case AudioAsset::typeKey: | |
| case BlobAsset::typeKey: | |
| case ScriptAsset::typeKey: | |
| case ScriptModuleAsset::typeKey: | |
| case ShaderAsset::typeKey: | |
| { | |
| auto fa = object->as<FileAsset>(); | |
| m_fileAssets.push_back(rcp<FileAsset>(fa)); | |
| if (object->coreType() == AudioAsset::typeKey) | |
| { | |
| m_hasAudio = true; | |
| } | |
| } | |
| break; | |
| case ViewModel::typeKey: | |
| { | |
| auto vmc = object->as<ViewModel>(); | |
| m_ViewModels.push_back(vmc); | |
| break; | |
| } | |
| case DataEnum::typeKey: | |
| case DataEnumCustom::typeKey: | |
| { | |
| auto de = object->as<DataEnum>(); | |
| m_Enums.push_back(de); | |
| break; | |
| } | |
| case ViewModelPropertyEnumCustom::typeKey: | |
| { | |
| auto vme = object->as<ViewModelPropertyEnumCustom>(); | |
| if (vme->enumId() < m_Enums.size()) | |
| { | |
| vme->dataEnum(m_Enums[vme->enumId()]); | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| else | |
| { | |
| if (lastBindableObject == object) | |
| { | |
| lastBindableObject = nullptr; | |
| } | |
| fprintf(stderr, | |
| "Failed to import object of type %d\n", | |
| object->coreType()); | |
| delete object; | |
| continue; | |
| } | |
| std::unique_ptr<ImportStackObject> stackObject = nullptr; | |
| auto stackType = object->coreType(); | |
| switch (stackType) | |
| { | |
| case Backboard::typeKey: | |
| stackObject = std::make_unique<BackboardImporter>( | |
| object->as<Backboard>()); | |
| static_cast<BackboardImporter*>(stackObject.get())->file(this); | |
| break; | |
| case Artboard::typeKey: | |
| stackObject = | |
| std::make_unique<ArtboardImporter>(object->as<Artboard>()); | |
| break; | |
| case DataEnumCustom::typeKey: | |
| stackObject = std::make_unique<EnumImporter>( | |
| object->as<DataEnumCustom>()); | |
| break; | |
| case LinearAnimation::typeKey: | |
| stackObject = std::make_unique<LinearAnimationImporter>( | |
| object->as<LinearAnimation>()); | |
| break; | |
| case KeyedObject::typeKey: | |
| stackObject = std::make_unique<KeyedObjectImporter>( | |
| object->as<KeyedObject>()); | |
| break; | |
| case KeyedProperty::typeKey: | |
| { | |
| auto importer = importStack.latest<LinearAnimationImporter>( | |
| LinearAnimation::typeKey); | |
| if (importer == nullptr) | |
| { | |
| return ImportResult::malformed; | |
| } | |
| stackObject = std::make_unique<KeyedPropertyImporter>( | |
| importer->animation(), | |
| object->as<KeyedProperty>()); | |
| break; | |
| } | |
| case StateMachine::typeKey: | |
| stackObject = std::make_unique<StateMachineImporter>( | |
| object->as<StateMachine>()); | |
| break; | |
| case StateMachineLayer::typeKey: | |
| { | |
| auto artboardImporter = | |
| importStack.latest<ArtboardImporter>(ArtboardBase::typeKey); | |
| if (artboardImporter == nullptr) | |
| { | |
| return ImportResult::malformed; | |
| } | |
| stackObject = std::make_unique<StateMachineLayerImporter>( | |
| object->as<StateMachineLayer>(), | |
| artboardImporter->artboard()); | |
| break; | |
| } | |
| case EntryState::typeKey: | |
| case ExitState::typeKey: | |
| case AnyState::typeKey: | |
| case AnimationState::typeKey: | |
| case BlendState1DViewModel::typeKey: | |
| case BlendState1DInput::typeKey: | |
| case BlendStateDirect::typeKey: | |
| stackObject = std::make_unique<LayerStateImporter>( | |
| object->as<LayerState>()); | |
| stackType = LayerState::typeKey; | |
| break; | |
| case StateTransition::typeKey: | |
| case BlendStateTransition::typeKey: | |
| stackObject = std::make_unique<StateTransitionImporter>( | |
| object->as<StateTransition>()); | |
| stackType = StateTransition::typeKey; | |
| break; | |
| case StateMachineListener::typeKey: | |
| case StateMachineListenerSingle::typeKey: | |
| stackObject = std::make_unique<StateMachineListenerImporter>( | |
| object->as<StateMachineListener>()); | |
| stackType = StateMachineListener::typeKey; | |
| break; | |
| case ImageAsset::typeKey: | |
| case FontAsset::typeKey: | |
| case AudioAsset::typeKey: | |
| case BlobAsset::typeKey: | |
| stackObject = | |
| std::make_unique<FileAssetImporter>(object->as<FileAsset>(), | |
| m_assetLoader, | |
| m_factory); | |
| stackType = FileAsset::typeKey; | |
| break; | |
| case ListenerInputTypeKeyboardBase::typeKey: | |
| stackObject = | |
| std::make_unique<ListenerInputTypeKeyboardImporter>( | |
| object->as<ListenerInputTypeKeyboard>()); | |
| stackType = ListenerInputTypeKeyboardBase::typeKey; | |
| break; | |
| case ListenerInputTypeGamepadBase::typeKey: | |
| stackObject = | |
| std::make_unique<ListenerInputTypeGamepadImporter>( | |
| object->as<ListenerInputTypeGamepad>()); | |
| stackType = ListenerInputTypeGamepadBase::typeKey; | |
| break; | |
| case ListenerInputTypeSemanticBase::typeKey: | |
| stackObject = | |
| std::make_unique<ListenerInputTypeSemanticImporter>( | |
| object->as<ListenerInputTypeSemantic>()); | |
| stackType = ListenerInputTypeSemanticBase::typeKey; | |
| break; | |
| #ifdef WITH_RIVE_SCRIPTING | |
| case ScriptAsset::typeKey: | |
| { | |
| auto scriptAsset = object->as<ScriptAsset>(); | |
| stackObject = | |
| std::make_unique<TextAssetImporter>(scriptAsset, | |
| m_assetLoader, | |
| m_factory, | |
| &inBandContent); | |
| stackType = FileAsset::typeKey; | |
| scriptAsset->file(this); | |
| break; | |
| } | |
| case ScriptModuleAsset::typeKey: | |
| { | |
| stackObject = std::make_unique<TextAssetImporter>( | |
| object->as<ScriptModuleAsset>(), | |
| m_assetLoader, | |
| m_factory, | |
| &inBandContent); | |
| stackType = FileAsset::typeKey; | |
| break; | |
| } | |
| case ShaderAsset::typeKey: | |
| { | |
| auto shaderAsset = object->as<ShaderAsset>(); | |
| stackObject = | |
| std::make_unique<TextAssetImporter>(shaderAsset, | |
| m_assetLoader, | |
| m_factory, | |
| &inBandContent); | |
| stackType = FileAsset::typeKey; | |
| break; | |
| } | |
| #endif | |
| case ManifestAsset::typeKey: | |
| stackObject = | |
| std::make_unique<FileAssetImporter>(object->as<FileAsset>(), | |
| m_assetLoader, | |
| m_factory); | |
| stackType = FileAsset::typeKey; | |
| m_manifest = rcp<FileAsset>(object->as<ManifestAsset>()); | |
| break; | |
| case ViewModel::typeKey: | |
| { | |
| stackObject = std::make_unique<ViewModelImporter>( | |
| object->as<ViewModel>()); | |
| stackType = ViewModel::typeKey; | |
| object->as<ViewModel>()->file(this); | |
| break; | |
| } | |
| case ViewModelInstance::typeKey: | |
| stackObject = std::make_unique<ViewModelInstanceImporter>( | |
| object->as<ViewModelInstance>()); | |
| stackType = ViewModelInstance::typeKey; | |
| break; | |
| case ViewModelInstanceList::typeKey: | |
| stackObject = std::make_unique<ViewModelInstanceListImporter>( | |
| object->as<ViewModelInstanceList>()); | |
| stackType = ViewModelInstanceList::typeKey; | |
| break; | |
| case TransitionViewModelCondition::typeKey: | |
| case TransitionArtboardCondition::typeKey: | |
| case TransitionFocusCondition::typeKey: | |
| stackObject = | |
| std::make_unique<TransitionViewModelConditionImporter>( | |
| object->as<TransitionViewModelCondition>()); | |
| stackType = TransitionViewModelCondition::typeKey; | |
| break; | |
| case BindablePropertyNumber::typeKey: | |
| case BindablePropertyString::typeKey: | |
| case BindablePropertyColor::typeKey: | |
| case BindablePropertyEnum::typeKey: | |
| case BindablePropertyBoolean::typeKey: | |
| case BindablePropertyAsset::typeKey: | |
| case BindablePropertyViewModel::typeKey: | |
| case BindablePropertyArtboard::typeKey: | |
| case BindablePropertyTrigger::typeKey: | |
| case BindablePropertyInteger::typeKey: | |
| case BindablePropertyList::typeKey: | |
| stackObject = std::make_unique<BindablePropertyImporter>( | |
| object->as<BindableProperty>()); | |
| stackType = BindablePropertyBase::typeKey; | |
| break; | |
| case DataConverterGroupBase::typeKey: | |
| stackObject = std::make_unique<DataConverterGroupImporter>( | |
| object->as<DataConverterGroup>()); | |
| stackType = DataConverterGroupBase::typeKey; | |
| break; | |
| case DataConverterFormulaBase::typeKey: | |
| stackObject = std::make_unique<DataConverterFormulaImporter>( | |
| object->as<DataConverterFormula>()); | |
| stackType = DataConverterFormulaBase::typeKey; | |
| break; | |
| case DataConverterNumberToList::typeKey: | |
| object->as<DataConverterNumberToList>()->file(this); | |
| break; | |
| case ArtboardComponentList::typeKey: | |
| object->as<ArtboardComponentList>()->file(this); | |
| break; | |
| case NestedArtboard::typeKey: | |
| case NestedArtboardLayout::typeKey: | |
| case NestedArtboardLeaf::typeKey: | |
| object->as<NestedArtboard>()->file(this); | |
| break; | |
| case ScriptedDataConverter::typeKey: | |
| case ScriptedDrawable::typeKey: | |
| case ScriptedLayout::typeKey: | |
| case ScriptedPathEffect::typeKey: | |
| case ScriptedListenerAction::typeKey: | |
| case ScriptedTransitionCondition::typeKey: | |
| case ScriptedInterpolator::typeKey: | |
| { | |
| auto scriptedObject = ScriptedObject::from(object); | |
| if (scriptedObject != nullptr) | |
| { | |
| stackObject = std::make_unique<ScriptedObjectImporter>( | |
| scriptedObject); | |
| stackType = ScriptedDrawable::typeKey; | |
| } | |
| break; | |
| } | |
| case DataBindPathBase::typeKey: | |
| stackObject = std::make_unique<DataBindPathImporter>( | |
| object->as<DataBindPath>()); | |
| stackType = DataBindPathBase::typeKey; | |
| break; | |
| case ScriptInputArtboard::typeKey: | |
| object->as<ScriptInputArtboard>()->file(this); | |
| break; | |
| } | |
| if (importStack.makeLatest(stackType, std::move(stackObject)) != | |
| StatusCode::Ok) | |
| { | |
| // Some previous stack item didn't resolve. | |
| return ImportResult::malformed; | |
| } | |
| if (object->is<StateMachineLayerComponent>() && | |
| importStack.makeLatest( | |
| StateMachineLayerComponent::typeKey, | |
| std::make_unique<StateMachineLayerComponentImporter>( | |
| object->as<StateMachineLayerComponent>())) != | |
| StatusCode::Ok) | |
| { | |
| return ImportResult::malformed; | |
| } | |
| if (object->is<DataConverter>()) | |
| { | |
| m_DataConverters.push_back(object->as<DataConverter>()); | |
| } | |
| else if (object->is<KeyFrameInterpolator>()) | |
| { | |
| // The file only owns the interpolators that don't belong to a | |
| // specific artboard | |
| auto artboardImporter = | |
| importStack.latest<ArtboardImporter>(ArtboardBase::typeKey); | |
| if (artboardImporter == nullptr) | |
| { | |
| m_keyframeInterpolators.push_back( | |
| object->as<KeyFrameInterpolator>()); | |
| } | |
| if (object->is<ScriptedInterpolator>()) | |
| { | |
| m_scriptedInterpolators.push_back( | |
| object->as<ScriptedInterpolator>()); | |
| } | |
| } | |
| else if (object->is<ScrollPhysics>()) | |
| { | |
| m_scrollPhysics.push_back(object->as<ScrollPhysics>()); | |
| } | |
| } | |
| auto resolved = importStack.resolve(); | |
| #ifdef WITH_RIVE_SCRIPTING | |
| registerScripts(); | |
| #endif | |
| return !reader.hasError() && resolved == StatusCode::Ok | |
| ? ImportResult::success | |
| : ImportResult::malformed; | |
| } | |
| void File::addFileViewModelInstance(ViewModelInstance* viewModelInstance) | |
| { | |
| m_ViewModelInstances.push_back(rcp<ViewModelInstance>(viewModelInstance)); | |
| } | |
| #ifdef WITH_RIVE_SCRIPTING | |
| #ifdef WITH_RIVE_SCRIPTING_WASM | |
| const char* File::frameBoundary() | |
| { | |
| const char* warning = nullptr; | |
| for (auto& vm : m_wasmVMs) | |
| { | |
| const char* notice = vm->frameBoundary(); | |
| if (warning == nullptr) | |
| { | |
| warning = notice; | |
| } | |
| } | |
| return warning; | |
| } | |
| #endif | |
| void File::registerScripts() | |
| { | |
| #ifdef WITH_RIVE_SCRIPTING_WASM | |
| // A wasm script module supersedes the bytecode path: scripts live inside | |
| // the module and ScriptAssets are metadata-only routing records. Assets | |
| // arrive grouped, each module followed by its scripts; scripts before the | |
| // first module belong to it, which keeps older single-module files | |
| // working. | |
| auto registerOn = [](WasmScriptingVM* vm, ScriptAsset* script) { | |
| int resultRef = 0; | |
| if (!vm->requireModule(script->moduleName(), &resultRef)) | |
| { | |
| fprintf(stderr, | |
| "wasm script module '%s' failed: %s\n", | |
| script->moduleName().c_str(), | |
| vm->lastError().c_str()); | |
| } | |
| else if (!script->isModule() && resultRef != 0) | |
| { | |
| // The module's result is the protocol script's generator; | |
| // storing the ref lets the standard lazy instantiation flow | |
| // run against the wasm backend. | |
| script->registrationComplete(resultRef); | |
| } | |
| script->wasmBackend(vm); | |
| }; | |
| std::vector<ScriptAsset*> pending; | |
| WasmScriptingVM* current = nullptr; | |
| for (auto& asset : m_fileAssets) | |
| { | |
| if (asset->is<ScriptModuleAsset>()) | |
| { | |
| auto module = asset->as<ScriptModuleAsset>(); | |
| #ifndef WITH_RIVE_TOOLS | |
| if (!module->verified()) | |
| { | |
| continue; | |
| } | |
| #endif | |
| std::string error; | |
| auto vm = WasmScriptingVM::make(module->module(), m_factory, error); | |
| if (vm == nullptr) | |
| { | |
| fprintf(stderr, | |
| "wasm script module failed to start: %s\n", | |
| error.c_str()); | |
| continue; | |
| } | |
| vm->viewModels(&m_ViewModels); | |
| vm->file(this); | |
| current = vm.get(); | |
| m_wasmVMs.push_back(std::move(vm)); | |
| for (auto* script : pending) | |
| { | |
| registerOn(current, script); | |
| } | |
| pending.clear(); | |
| } | |
| else if (asset->is<ScriptAsset>() && current == nullptr) | |
| { | |
| pending.push_back(asset->as<ScriptAsset>()); | |
| } | |
| else if (asset->is<ScriptAsset>()) | |
| { | |
| registerOn(current, asset->as<ScriptAsset>()); | |
| } | |
| } | |
| // Bytecode-only files can still run on the wasm backend: with | |
| // RIVE_WASM_VM naming a stock vm module, every script registers | |
| // dynamically instead of arriving baked into a module asset. Dev and | |
| // sweep-harness lane; bytecode is interpreted by the module's Luau VM. | |
| if (m_wasmVMs.empty() && !pending.empty()) | |
| { | |
| if (const char* vmPath = getenv("RIVE_WASM_VM")) | |
| { | |
| FILE* vmFile = fopen(vmPath, "rb"); | |
| if (vmFile != nullptr) | |
| { | |
| fseek(vmFile, 0, SEEK_END); | |
| long size = ftell(vmFile); | |
| fseek(vmFile, 0, SEEK_SET); | |
| std::vector<uint8_t> vmBytes(size); | |
| size_t read = fread(vmBytes.data(), 1, size, vmFile); | |
| fclose(vmFile); | |
| std::string error; | |
| auto vm = WasmScriptingVM::make( | |
| Span<const uint8_t>(vmBytes.data(), read), | |
| m_factory, | |
| error); | |
| if (vm == nullptr) | |
| { | |
| fprintf(stderr, | |
| "wasm bytecode lane failed to start: %s\n", | |
| error.c_str()); | |
| } | |
| else | |
| { | |
| vm->viewModels(&m_ViewModels); | |
| vm->file(this); | |
| current = vm.get(); | |
| m_wasmVMs.push_back(std::move(vm)); | |
| for (auto* script : pending) | |
| { | |
| current->registerBytecode(script->moduleName(), | |
| script->moduleBytecode()); | |
| } | |
| for (auto* script : pending) | |
| { | |
| registerOn(current, script); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (!m_wasmVMs.empty()) | |
| { | |
| return; | |
| } | |
| #endif | |
| #ifdef WITH_RIVE_SCRIPTING_LUAU | |
| // Check if we have any script assets in the file | |
| std::vector<ScriptAsset*> scripts; | |
| for (auto asset : m_fileAssets) | |
| { | |
| if (asset->is<ScriptAsset>()) | |
| { | |
| scripts.push_back(asset->as<ScriptAsset>()); | |
| } | |
| } | |
| // Only make the ScriptingVM if we have any script assets | |
| if (!scripts.empty()) | |
| { | |
| // If no VM was provided (e.g., from editor), create our own. | |
| if (m_scriptingVM == nullptr) | |
| { | |
| makeScriptingVM(); | |
| } | |
| ScriptingVM* vm = m_scriptingVM.get(); | |
| if (vm != nullptr) | |
| { | |
| routeScriptingToImportFactory(vm->context()); | |
| // Set up the Data global (view model constructors) on the active | |
| // VM, whether it was created here or supplied externally (e.g. by | |
| // the CommandServer). Skip it when the VM's owner builds Data | |
| // itself, as the editor does in Dart. | |
| if (!vm->context()->initializesDataGlobalExternally()) | |
| { | |
| initializeLuaData(vm->state(), m_ViewModels); | |
| } | |
| for (auto scriptAsset : scripts) | |
| { | |
| // At runtime, if the script is verified, add it to be | |
| // registered with the VM. At edit time, the script will | |
| // have already been registered, so this won't run. | |
| // WITH_RIVE_TOOLS allows unverified scripts for testing. | |
| #ifdef WITH_RIVE_TOOLS | |
| vm->addModule(scriptAsset); | |
| #else | |
| if (scriptAsset->verified()) | |
| { | |
| vm->addModule(scriptAsset); | |
| } | |
| #endif | |
| } | |
| // Perform registration - ScriptingContext will handle dependencies | |
| // and retries | |
| vm->performRegistration(); | |
| } | |
| for (auto& interpolator : m_scriptedInterpolators) | |
| { | |
| auto scriptAsset = interpolator->scriptAsset(); | |
| if (scriptAsset != nullptr) | |
| { | |
| scriptAsset->initScriptedObject(interpolator); | |
| } | |
| } | |
| } | |
| #endif | |
| } | |
| #ifdef WITH_RIVE_SCRIPTING_LUAU | |
| // Scripts reach the GPU through their ScriptingContext, and nothing else in | |
| // the import path hands them one, so a script that opened a gpuCanvas used to | |
| // fail on every runtime host. Ore and canvas host routing derive from the | |
| // context's own factory at read time, so route by making that factory the one | |
| // the file imported through, ahead of performRegistration since registration | |
| // can run script bodies. | |
| void File::routeScriptingToImportFactory(ScriptingContext* context) | |
| { | |
| if (context == nullptr || m_factory == nullptr) | |
| { | |
| return; | |
| } | |
| // A VM the caller handed in was built before decode chose a recording | |
| // session, so its scripts would draw to the driver while the file records. | |
| if (context->factory() != m_factory) | |
| { | |
| context->adoptImportFactory(m_factory); | |
| } | |
| // The render context has its own late bind flag, and a caller that already | |
| // satisfied it outranks the factory's default. | |
| if (context->renderContextIsLateBound()) | |
| { | |
| if (Factory* renderContext = m_factory->renderContext()) | |
| { | |
| context->setRenderContext(renderContext); | |
| } | |
| } | |
| } | |
| void File::makeScriptingVM() | |
| { | |
| cleanupScriptingVM(); | |
| auto context = std::make_unique<CPPRuntimeScriptingContext>(m_factory); | |
| m_scriptingVM = make_rcp<ScriptingVM>(std::move(context)); | |
| } | |
| lua_State* File::scriptingState() | |
| { | |
| return m_scriptingVM ? m_scriptingVM->state() : nullptr; | |
| } | |
| void File::setScriptingVM(rcp<ScriptingVM> vm) | |
| { | |
| #ifdef WITH_RIVE_TOOLS | |
| if (m_scriptingVM != nullptr) | |
| { | |
| ScriptingContext* context = m_scriptingVM->context(); | |
| if (context != nullptr) | |
| { | |
| context->disposeOrphanScriptedProperties(); | |
| } | |
| } | |
| #endif | |
| m_scriptingVM = std::move(vm); | |
| } | |
| void File::cleanupScriptingVM() | |
| { | |
| #ifdef WITH_RIVE_TOOLS | |
| if (m_scriptingVM != nullptr) | |
| { | |
| ScriptingContext* context = m_scriptingVM->context(); | |
| if (context != nullptr) | |
| { | |
| context->disposeOrphanScriptedProperties(); | |
| } | |
| } | |
| #endif | |
| // ScriptedObjects only hold a raw ScriptingVM* (see | |
| // ScriptingVM::registerScriptedObject), so dropping our rcp here is the | |
| // only thing keeping the VM alive from File's side. If Dart still holds | |
| // its own rcp<ScriptingVM> (editor flow), the VM and lua_State stay | |
| // alive until Dart releases too. ~ScriptingVM handles lua_close. | |
| m_scriptingVM = nullptr; | |
| } | |
| #endif | |
| #endif | |
| Artboard* File::artboard(std::string name) const | |
| { | |
| for (const auto& artboard : m_artboards) | |
| { | |
| if (artboard->name() == name) | |
| { | |
| return artboard; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| Artboard* File::artboard() const | |
| { | |
| if (m_artboards.empty()) | |
| { | |
| return nullptr; | |
| } | |
| return m_artboards[0]; | |
| } | |
| Artboard* File::artboard(size_t index) const | |
| { | |
| if (index >= m_artboards.size()) | |
| { | |
| return nullptr; | |
| } | |
| return m_artboards[index]; | |
| } | |
| std::string File::artboardNameAt(size_t index) const | |
| { | |
| auto ab = this->artboard(index); | |
| return ab ? ab->name() : ""; | |
| } | |
| std::unique_ptr<ArtboardInstance> File::instanceArtboard(Artboard* ab) const | |
| { | |
| if (ab) | |
| { | |
| auto artboardInstance = ab->instance(); | |
| #ifdef WITH_RIVE_SCRIPTING_LUAU | |
| artboardInstance->scriptingVM(m_scriptingVM); | |
| #endif | |
| artboardInstance->file(ref_rcp(this)); | |
| // Root instances own the FocusManager for their whole tree; nested | |
| // artboards and component-list items adopt it. Established here rather | |
| // than by the first state machine so the manager outlives every state | |
| // machine built against it, and so the tree is built once instead of | |
| // rebuilt per state machine. | |
| artboardInstance->buildFocusTree(artboardInstance->ensureFocusManager(), | |
| nullptr); | |
| // Global view model instances are no longer auto-created here. Callers | |
| // (e.g. the high-level runtime's autoBind, or explicit | |
| // setGlobalViewModelInstance) create and bind them on demand. | |
| return artboardInstance; | |
| } | |
| return nullptr; | |
| } | |
| std::unique_ptr<ArtboardInstance> File::artboardDefault() const | |
| { | |
| auto ab = this->artboard(); | |
| return instanceArtboard(ab); | |
| } | |
| std::unique_ptr<ArtboardInstance> File::artboardAt(size_t index) const | |
| { | |
| auto ab = this->artboard(index); | |
| return instanceArtboard(ab); | |
| } | |
| std::unique_ptr<ArtboardInstance> File::artboardNamed(std::string name) const | |
| { | |
| auto ab = this->artboard(name); | |
| return instanceArtboard(ab); | |
| } | |
| rcp<BindableArtboard> File::bindableArtboardNamed(std::string name) const | |
| { | |
| auto ab = this->artboardNamed(name); | |
| return ab ? make_rcp<BindableArtboard>(ref_rcp(this), std::move(ab)) | |
| : nullptr; | |
| } | |
| rcp<BindableArtboard> File::bindableArtboardDefault() const | |
| { | |
| auto ab = this->artboardDefault(); | |
| return ab ? make_rcp<BindableArtboard>(ref_rcp(this), std::move(ab)) | |
| : nullptr; | |
| } | |
| rcp<BindableArtboard> File::internalBindableArtboardFromArtboard( | |
| Artboard* artboard) const | |
| { | |
| auto ab = artboard ? artboard->instance() : nullptr; | |
| return ab ? make_rcp<BindableArtboard>(nullptr, std::move(ab)) : nullptr; | |
| } | |
| void File::completeViewModelInstance( | |
| rcp<ViewModelInstance> viewModelInstance) const | |
| { | |
| std::unordered_map<ViewModelInstance*, rcp<ViewModelInstance>> instancesMap; | |
| completeViewModelInstance(viewModelInstance, instancesMap); | |
| } | |
| void File::completeViewModelInstance( | |
| rcp<ViewModelInstance> viewModelInstance, | |
| std::unordered_map<ViewModelInstance*, rcp<ViewModelInstance>>& | |
| instancesMap) const | |
| { | |
| auto viewModel = m_ViewModels[viewModelInstance->viewModelId()]; | |
| auto propertyValues = viewModelInstance->propertyValues(); | |
| for (auto& value : propertyValues) | |
| { | |
| if (value->is<ViewModelInstanceViewModel>()) | |
| { | |
| auto property = viewModel->property(value->viewModelPropertyId()); | |
| if (property->is<ViewModelPropertyViewModel>()) | |
| { | |
| auto valueViewModel = value->as<ViewModelInstanceViewModel>(); | |
| auto propertViewModel = | |
| property->as<ViewModelPropertyViewModel>(); | |
| auto viewModelReference = | |
| m_ViewModels[propertViewModel->viewModelReferenceId()]; | |
| auto viewModelReferenceInstance = viewModelReference->instance( | |
| valueViewModel->propertyValue()); | |
| valueViewModel->parentViewModelInstance( | |
| viewModelInstance.get()); | |
| if (viewModelReferenceInstance != nullptr) | |
| { | |
| auto itr = instancesMap.find(viewModelReferenceInstance); | |
| if (itr == instancesMap.end()) | |
| { | |
| auto viewModelReferenceInstanceCopy = | |
| copyViewModelInstance(viewModelReferenceInstance, | |
| instancesMap); | |
| instancesMap[viewModelReferenceInstance] = | |
| viewModelReferenceInstanceCopy; | |
| valueViewModel->referenceViewModelInstance( | |
| viewModelReferenceInstanceCopy); | |
| } | |
| else | |
| { | |
| valueViewModel->referenceViewModelInstance(itr->second); | |
| } | |
| } | |
| } | |
| } | |
| else if (value->is<ViewModelInstanceList>()) | |
| { | |
| auto viewModelList = value->as<ViewModelInstanceList>(); | |
| viewModelList->parentViewModelInstance(viewModelInstance.get()); | |
| for (auto& listItem : viewModelList->listItems()) | |
| { | |
| auto viewModel = m_ViewModels[listItem->viewModelId()]; | |
| auto viewModelListItemInstance = | |
| viewModel->instance(listItem->viewModelInstanceId()); | |
| if (viewModelListItemInstance != nullptr) | |
| { | |
| auto itr = instancesMap.find(viewModelListItemInstance); | |
| if (itr == instancesMap.end()) | |
| { | |
| auto viewModelInstanceListItemCopy = | |
| copyViewModelInstance(viewModelListItemInstance, | |
| instancesMap); | |
| instancesMap[viewModelListItemInstance] = | |
| viewModelInstanceListItemCopy; | |
| listItem->viewModelInstance( | |
| viewModelInstanceListItemCopy); | |
| } | |
| else | |
| { | |
| listItem->viewModelInstance(itr->second); | |
| } | |
| } | |
| } | |
| } | |
| value->viewModelProperty( | |
| viewModel->property(value->viewModelPropertyId())); | |
| } | |
| } | |
| void File::completeViewModelProperties(ViewModelInstance* viewModelInstance) | |
| { | |
| auto viewModel = m_ViewModels[viewModelInstance->viewModelId()]; | |
| auto propertyValues = viewModelInstance->propertyValues(); | |
| for (auto& value : propertyValues) | |
| { | |
| if (value->is<ViewModelInstanceViewModel>()) | |
| { | |
| auto property = viewModel->property(value->viewModelPropertyId()); | |
| if (property->is<ViewModelPropertyViewModel>()) | |
| { | |
| auto valueViewModel = value->as<ViewModelInstanceViewModel>(); | |
| auto propertViewModel = | |
| property->as<ViewModelPropertyViewModel>(); | |
| auto viewModelReference = | |
| m_ViewModels[propertViewModel->viewModelReferenceId()]; | |
| auto viewModelReferenceInstance = viewModelReference->instance( | |
| valueViewModel->propertyValue()); | |
| if (viewModelReferenceInstance != nullptr) | |
| { | |
| completeViewModelProperties(viewModelReferenceInstance); | |
| } | |
| } | |
| } | |
| else if (value->is<ViewModelInstanceList>()) | |
| { | |
| auto viewModelList = value->as<ViewModelInstanceList>(); | |
| for (auto& listItem : viewModelList->listItems()) | |
| { | |
| auto viewModel = m_ViewModels[listItem->viewModelId()]; | |
| auto viewModelListItemInstance = | |
| viewModel->instance(listItem->viewModelInstanceId()); | |
| if (viewModelListItemInstance != nullptr) | |
| { | |
| completeViewModelProperties(viewModelListItemInstance); | |
| } | |
| } | |
| } | |
| value->viewModelProperty( | |
| viewModel->property(value->viewModelPropertyId())); | |
| } | |
| } | |
| rcp<ViewModelInstance> File::copyViewModelInstance( | |
| ViewModelInstance* viewModelInstance, | |
| std::unordered_map<ViewModelInstance*, rcp<ViewModelInstance>>& | |
| instancesMap) const | |
| { | |
| auto copy = rcp<ViewModelInstance>( | |
| viewModelInstance->clone()->as<ViewModelInstance>()); | |
| completeViewModelInstance(copy, instancesMap); | |
| #ifdef WITH_RIVE_TOOLS | |
| if (copy) | |
| { | |
| registerViewModelInstance(copy.get(), copy); | |
| } | |
| #endif | |
| return copy; | |
| } | |
| rcp<ViewModelInstance> File::createViewModelInstance(std::string name) const | |
| { | |
| for (auto& viewModel : m_ViewModels) | |
| { | |
| if (viewModel->is<ViewModel>()) | |
| { | |
| if (viewModel->name() == name) | |
| { | |
| return createViewModelInstance(viewModel); | |
| } | |
| } | |
| } | |
| return nullptr; | |
| } | |
| rcp<ViewModelInstance> File::createViewModelInstance( | |
| const std::string& name, | |
| const std::string& instanceName) const | |
| { | |
| for (auto& viewModel : m_ViewModels) | |
| { | |
| if (viewModel->name() == name) | |
| { | |
| auto instance = viewModel->instance(instanceName); | |
| if (instance != nullptr) | |
| { | |
| std::unordered_map<ViewModelInstance*, rcp<ViewModelInstance>> | |
| instancesMap; | |
| return copyViewModelInstance(instance, instancesMap); | |
| } | |
| } | |
| } | |
| return nullptr; | |
| } | |
| rcp<ViewModelInstance> File::createViewModelInstance(size_t index, | |
| size_t instanceIndex) const | |
| { | |
| if (index < m_ViewModels.size()) | |
| { | |
| auto viewModel = m_ViewModels[index]; | |
| auto instance = viewModel->instance(instanceIndex); | |
| if (instance != nullptr) | |
| { | |
| std::unordered_map<ViewModelInstance*, rcp<ViewModelInstance>> | |
| instancesMap; | |
| return copyViewModelInstance(instance, instancesMap); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| uint32_t File::findViewModelId(ViewModel* search) const | |
| { | |
| uint32_t viewModelId = 0; | |
| for (auto& viewModel : m_ViewModels) | |
| { | |
| if (viewModel == search) | |
| { | |
| break; | |
| } | |
| viewModelId++; | |
| } | |
| return viewModelId; | |
| } | |
| #ifdef WITH_RIVE_TOOLS | |
| void File::setViewModelInstanceRegistrar(ViewModelInstanceRegistrar* registrar) | |
| { | |
| m_viewModelInstanceRegistrar = registrar; | |
| } | |
| void File::registerViewModelInstance(ViewModelInstance* ptr, | |
| rcp<ViewModelInstance> ref) const | |
| { | |
| if (ptr != nullptr && m_viewModelInstanceRegistrar != nullptr) | |
| { | |
| m_viewModelInstanceRegistrar->registerInstance(ptr, std::move(ref)); | |
| } | |
| } | |
| bool File::containsViewModelInstance(ViewModelInstance* ptr) const | |
| { | |
| return m_viewModelInstanceRegistrar != nullptr && | |
| m_viewModelInstanceRegistrar->contains(ptr); | |
| } | |
| void File::clearRuntimeViewModelInstances() | |
| { | |
| if (m_viewModelInstanceRegistrar != nullptr) | |
| { | |
| m_viewModelInstanceRegistrar->clear(); | |
| } | |
| } | |
| #endif | |
| rcp<ViewModelInstance> File::createViewModelInstance(ViewModel* viewModel) const | |
| { | |
| if (viewModel != nullptr) | |
| { | |
| uint32_t viewModelId = findViewModelId(viewModel); | |
| auto viewModelInstance = new ViewModelInstance(); | |
| viewModelInstance->viewModelId(viewModelId); | |
| viewModelInstance->viewModel(viewModel); | |
| auto properties = viewModel->properties(); | |
| uint32_t propertyId = 0; | |
| for (auto& property : properties) | |
| { | |
| ViewModelInstanceValue* viewModelInstanceValue = nullptr; | |
| switch (property->coreType()) | |
| { | |
| case ViewModelPropertyStringBase::typeKey: | |
| viewModelInstanceValue = new ViewModelInstanceString(); | |
| break; | |
| case ViewModelPropertyNumberBase::typeKey: | |
| viewModelInstanceValue = new ViewModelInstanceNumber(); | |
| break; | |
| case ViewModelPropertyBooleanBase::typeKey: | |
| viewModelInstanceValue = new ViewModelInstanceBoolean(); | |
| break; | |
| case ViewModelPropertyColorBase::typeKey: | |
| viewModelInstanceValue = new ViewModelInstanceColor(); | |
| break; | |
| case ViewModelPropertyListBase::typeKey: | |
| viewModelInstanceValue = new ViewModelInstanceList(); | |
| viewModelInstanceValue->as<ViewModelInstanceList>() | |
| ->parentViewModelInstance(viewModelInstance); | |
| break; | |
| case ViewModelPropertyEnumSystemBase::typeKey: | |
| case ViewModelPropertyEnumCustomBase::typeKey: | |
| case ViewModelPropertyEnumBase::typeKey: | |
| viewModelInstanceValue = new ViewModelInstanceEnum(); | |
| break; | |
| case ViewModelPropertyTriggerBase::typeKey: | |
| viewModelInstanceValue = new ViewModelInstanceTrigger(); | |
| break; | |
| case ViewModelPropertyViewModelBase::typeKey: | |
| { | |
| viewModelInstanceValue = new ViewModelInstanceViewModel(); | |
| auto propertViewModel = | |
| property->as<ViewModelPropertyViewModel>(); | |
| auto viewModelReference = | |
| m_ViewModels[propertViewModel->viewModelReferenceId()]; | |
| auto viewModelInstanceViewModel = | |
| viewModelInstanceValue | |
| ->as<ViewModelInstanceViewModel>(); | |
| auto referenceViewModelInstance = | |
| createViewModelInstance(viewModelReference); | |
| if (referenceViewModelInstance) | |
| { | |
| viewModelInstanceViewModel->parentViewModelInstance( | |
| viewModelInstance); | |
| viewModelInstanceViewModel->referenceViewModelInstance( | |
| referenceViewModelInstance); | |
| } | |
| } | |
| break; | |
| case ViewModelPropertyAssetImageBase::typeKey: | |
| viewModelInstanceValue = new ViewModelInstanceAssetImage(); | |
| break; | |
| case ViewModelPropertyAssetFontBase::typeKey: | |
| viewModelInstanceValue = new ViewModelInstanceAssetFont(); | |
| break; | |
| case ViewModelPropertyAssetBlobBase::typeKey: | |
| viewModelInstanceValue = new ViewModelInstanceAssetBlob(); | |
| break; | |
| case ViewModelPropertySymbolListIndexBase::typeKey: | |
| viewModelInstanceValue = | |
| new ViewModelInstanceSymbolListIndex(); | |
| break; | |
| case ViewModelPropertyArtboardBase::typeKey: | |
| viewModelInstanceValue = new ViewModelInstanceArtboard(); | |
| break; | |
| default: | |
| fprintf(stderr, "Missing view model property type\n"); | |
| break; | |
| } | |
| if (viewModelInstanceValue != nullptr) | |
| { | |
| viewModelInstanceValue->viewModelProperty(property); | |
| viewModelInstanceValue->viewModelPropertyId(propertyId); | |
| } | |
| viewModelInstance->addValue(viewModelInstanceValue); | |
| propertyId++; | |
| } | |
| #ifdef WITH_RIVE_TOOLS | |
| if (viewModelInstance) | |
| { | |
| auto result = rcp<ViewModelInstance>(viewModelInstance); | |
| registerViewModelInstance(viewModelInstance, result); | |
| return result; | |
| } | |
| #endif | |
| return rcp<ViewModelInstance>(viewModelInstance); | |
| } | |
| return nullptr; | |
| } | |
| rcp<ViewModelInstance> File::createViewModelInstance(Artboard* artboard) const | |
| { | |
| if ((size_t)artboard->viewModelId() < m_ViewModels.size()) | |
| { | |
| auto viewModel = m_ViewModels[artboard->viewModelId()]; | |
| if (viewModel != nullptr) | |
| { | |
| return createViewModelInstance(viewModel); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| rcp<ViewModelInstance> File::createDefaultViewModelInstance( | |
| Artboard* artboard) const | |
| { | |
| if ((size_t)artboard->viewModelId() < m_ViewModels.size()) | |
| { | |
| auto viewModel = m_ViewModels[artboard->viewModelId()]; | |
| if (viewModel != nullptr) | |
| { | |
| return createDefaultViewModelInstance(viewModel); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| rcp<ViewModelInstance> File::createDefaultViewModelInstance( | |
| ViewModel* viewModel) const | |
| { | |
| auto viewModelInstance = viewModel->instance(0); | |
| if (viewModelInstance != nullptr) | |
| { | |
| auto copy = rcp<ViewModelInstance>( | |
| viewModelInstance->clone()->as<ViewModelInstance>()); | |
| completeViewModelInstance(copy); | |
| #ifdef WITH_RIVE_TOOLS | |
| if (copy) | |
| { | |
| registerViewModelInstance(copy.get(), copy); | |
| } | |
| #endif | |
| return copy; | |
| } | |
| return createViewModelInstance(viewModel); | |
| } | |
| ViewModelInstanceListItem* File::viewModelInstanceListItem( | |
| rcp<ViewModelInstance> viewModelInstance) | |
| { | |
| // Search for an implicit artboard linked to the viewModel. | |
| // It will return the first one it finds, but there could be more. | |
| // We should decide if we want to be more restrictive and only return | |
| // an artboard if one and only one is found. | |
| for (auto& artboard : m_artboards) | |
| { | |
| if (artboard->viewModelId() == viewModelInstance->viewModelId()) | |
| { | |
| return viewModelInstanceListItem(viewModelInstance, artboard); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| ViewModelInstanceListItem* File::viewModelInstanceListItem( | |
| rcp<ViewModelInstance> viewModelInstance, | |
| Artboard* artboard) | |
| { | |
| auto viewModelInstanceListItem = new ViewModelInstanceListItem(); | |
| viewModelInstanceListItem->viewModelInstance(viewModelInstance); | |
| viewModelInstanceListItem->artboard(artboard); | |
| return viewModelInstanceListItem; | |
| } | |
| ViewModel* File::viewModel(std::string name) | |
| { | |
| for (auto& viewModel : m_ViewModels) | |
| { | |
| if (viewModel->name() == name) | |
| { | |
| return viewModel; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| ViewModel* File::viewModel(size_t index) const | |
| { | |
| if (index < m_ViewModels.size()) | |
| { | |
| return m_ViewModels[index]; | |
| } | |
| return nullptr; | |
| } | |
| uint32_t File::viewModelId(const std::string& name) const | |
| { | |
| for (uint32_t i = 0; i < m_ViewModels.size(); i++) | |
| { | |
| auto vm = m_ViewModels[i]; | |
| if (vm != nullptr && vm->name() == name) | |
| { | |
| return i; | |
| } | |
| } | |
| return static_cast<uint32_t>(m_ViewModels.size()); | |
| } | |
| std::vector<ViewModel*> File::globalViewModels() const | |
| { | |
| std::vector<ViewModel*> viewModels; | |
| for (ViewModel* vm : m_ViewModels) | |
| { | |
| if (vm != nullptr && static_cast<ViewModelType>(vm->viewModelType()) == | |
| ViewModelType::global) | |
| { | |
| viewModels.push_back(vm); | |
| } | |
| } | |
| return viewModels; | |
| } | |
| std::vector<std::string> File::globalViewModelNames() const | |
| { | |
| std::vector<std::string> names; | |
| for (ViewModel* vm : globalViewModels()) | |
| { | |
| names.push_back(vm->name()); | |
| } | |
| return names; | |
| } | |
| ViewModelRuntime* File::viewModelByIndex(size_t index) const | |
| { | |
| if (index < m_ViewModels.size()) | |
| { | |
| return createViewModelRuntime(m_ViewModels[index]).get(); | |
| } | |
| fprintf(stderr, | |
| "Could not find View Model. Index %zu is out of range.\n", | |
| index); | |
| return nullptr; | |
| } | |
| ViewModelRuntime* File::viewModelByName(std::string name) const | |
| { | |
| for (auto& viewModel : m_ViewModels) | |
| { | |
| if (viewModel->name() == name) | |
| { | |
| return createViewModelRuntime(viewModel).get(); | |
| } | |
| } | |
| fprintf(stderr, "Could not find View Model named %s.\n", name.c_str()); | |
| return nullptr; | |
| } | |
| ViewModelRuntime* File::defaultArtboardViewModel(Artboard* artboard) const | |
| { | |
| if (artboard == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| if ((size_t)artboard->viewModelId() < m_ViewModels.size()) | |
| { | |
| auto viewModel = m_ViewModels[artboard->viewModelId()]; | |
| return createViewModelRuntime(viewModel).get(); | |
| } | |
| fprintf(stderr, | |
| "Could not find a View Model linked to Artboard %s.\n", | |
| artboard->name().c_str()); | |
| return nullptr; | |
| } | |
| rcp<ViewModelRuntime> File::createViewModelRuntime(ViewModel* viewModel) const | |
| { | |
| auto viewModelRuntime = make_rcp<ViewModelRuntime>(viewModel, this); | |
| m_viewModelRuntimes.push_back(viewModelRuntime); | |
| return viewModelRuntime; | |
| } | |
| Span<const rcp<FileAsset>> File::assets() const { return m_fileAssets; } | |
| const std::vector<DataEnum*>& File::enums() const { return m_Enums; } | |
| #ifdef WITH_RIVE_TOOLS | |
| const std::vector<uint8_t> File::stripAssets(Span<const uint8_t> bytes, | |
| std::set<uint16_t> typeKeys, | |
| ImportResult* result) | |
| { | |
| std::vector<uint8_t> strippedData; | |
| strippedData.reserve(bytes.size()); | |
| BinaryReader reader(bytes); | |
| RuntimeHeader header; | |
| if (!RuntimeHeader::read(reader, header)) | |
| { | |
| if (result) | |
| { | |
| *result = ImportResult::malformed; | |
| } | |
| } | |
| else if (header.majorVersion() != majorVersion) | |
| { | |
| if (result) | |
| { | |
| *result = ImportResult::unsupportedVersion; | |
| } | |
| } | |
| else | |
| { | |
| strippedData.insert(strippedData.end(), | |
| bytes.data(), | |
| reader.position()); | |
| const uint8_t* from = reader.position(); | |
| const uint8_t* to = reader.position(); | |
| uint16_t lastAssetType = 0; | |
| while (!reader.reachedEnd()) | |
| { | |
| auto object = readRuntimeObject(reader, header); | |
| if (object == nullptr) | |
| { | |
| continue; | |
| } | |
| if (object->is<FileAssetBase>()) | |
| { | |
| lastAssetType = object->coreType(); | |
| } | |
| if (object->is<FileAssetContents>() && | |
| typeKeys.find(lastAssetType) != typeKeys.end()) | |
| { | |
| if (from != to) | |
| { | |
| strippedData.insert(strippedData.end(), from, to); | |
| } | |
| from = reader.position(); | |
| } | |
| delete object; | |
| to = reader.position(); | |
| } | |
| if (from != to) | |
| { | |
| strippedData.insert(strippedData.end(), from, to); | |
| } | |
| *result = ImportResult::success; | |
| } | |
| return strippedData; | |
| } | |
| #endif | |
| rcp<FileAsset> File::asset(size_t index) | |
| { | |
| if (index >= 0 && index < m_fileAssets.size()) | |
| { | |
| return m_fileAssets[index]; | |
| } | |
| return nullptr; | |
| } |