ApplicationStateValidator.cpp (9621B)
1 /*---------------------------------------------------------------------------------------- 2 3 Copyright (c) 2023 AUDIOKINETIC Inc. 4 5 This file is licensed to use under the license available at: 6 https://github.com/audiokinetic/ReaWwise/blob/main/License.txt (the "License"). 7 You may not use this file except in compliance with the License. 8 9 Unless required by applicable law or agreed to in writing, software distributed 10 under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 specific language governing permissions and limitations under the License. 13 14 ----------------------------------------------------------------------------------------*/ 15 16 #include "ApplicationStateValidator.h" 17 18 #include "Helpers/ImportHelper.h" 19 #include "Helpers/WwiseHelper.h" 20 #include "Model/IDs.h" 21 22 namespace AK::WwiseTransfer::ApplicationState 23 { 24 Validator::Validator(juce::ValueTree appState, WaapiClient& waapiClient) 25 : applicationState(appState) 26 , waapiClient(waapiClient) 27 { 28 applicationState.addListener(this); 29 } 30 31 Validator::~Validator() 32 { 33 applicationState.removeListener(this); 34 } 35 36 void Validator::valueTreePropertyChanged(juce::ValueTree& valueTree, const juce::Identifier& property) 37 { 38 if(valueTree.getType() == IDs::application) 39 { 40 if(property == IDs::originalsSubfolder || property == IDs::languageSubfolder) 41 { 42 const juce::String originalsFolder = valueTree[IDs::originalsFolder]; 43 const juce::String originalsSubfolder = valueTree[IDs::originalsSubfolder]; 44 const juce::String languageSubfolder = valueTree[IDs::languageSubfolder]; 45 46 auto isValid = validateOriginalsSubfolder(originalsFolder, languageSubfolder, originalsSubfolder); 47 juce::String errorMessage = isValid ? "" : "Invalid originals subfolder"; 48 49 valueTree.setPropertyExcludingListener(this, IDs::originalsSubfolderValid, isValid, nullptr); 50 valueTree.setPropertyExcludingListener(this, IDs::originalsSubfolderErrorMessage, errorMessage, nullptr); 51 } 52 else if(property == IDs::importDestination) 53 { 54 const juce::String importDestination = valueTree[IDs::importDestination]; 55 56 auto isValid = validateImportDestination(importDestination); 57 juce::String errorMessage = isValid ? "" : "Invalid import destination"; 58 59 valueTree.setPropertyExcludingListener(this, IDs::importDestinationValid, isValid, nullptr); 60 valueTree.setPropertyExcludingListener(this, IDs::importDestinationErrorMessage, errorMessage, nullptr); 61 62 auto onGetObjectAsync = [this](const Waapi::Response<Waapi::ObjectResponse> response) 63 { 64 auto objectType = Wwise::ObjectType::VirtualFolder; 65 66 if(response.result.path.isNotEmpty()) 67 objectType = response.result.type; 68 69 applicationState.setProperty(IDs::importDestinationType, juce::VariantConverter<Wwise::ObjectType>::toVar(objectType), nullptr); 70 }; 71 72 waapiClient.getObjectAsync(importDestination, onGetObjectAsync); 73 } 74 else if(property == IDs::importDestinationType) 75 { 76 const auto importDestinationType = juce::VariantConverter<Wwise::ObjectType>::fromVar(valueTree[IDs::importDestinationType]); 77 78 validateHierarchyMapping(importDestinationType, applicationState.getChildWithName(IDs::hierarchyMapping)); 79 } 80 } 81 else if(valueTree.getType() == IDs::hierarchyMappingNode) 82 { 83 if(property == IDs::objectType) 84 { 85 auto hierarchyMapping = valueTree.getParent(); 86 87 const auto importDestinationType = juce::VariantConverter<Wwise::ObjectType>::fromVar(applicationState[IDs::importDestinationType]); 88 validateHierarchyMapping(importDestinationType, hierarchyMapping); 89 } 90 else if(property == IDs::propertyTemplatePath || property == IDs::propertyTemplatePathType) 91 { 92 validatePropertyTemplatePath(valueTree); 93 } 94 else if(property == IDs::objectName) 95 { 96 validateObjectName(valueTree); 97 } 98 } 99 } 100 101 void Validator::valueTreeChildAdded(juce::ValueTree& parent, juce::ValueTree& child) 102 { 103 if(parent.getType() == IDs::hierarchyMapping) 104 { 105 const auto importDestinationType = juce::VariantConverter<Wwise::ObjectType>::fromVar(applicationState[IDs::importDestinationType]); 106 107 validateHierarchyMapping(importDestinationType, parent); 108 } 109 110 if(child.getType() == IDs::hierarchyMappingNode) 111 { 112 validatePropertyTemplatePath(child); 113 validateObjectName(child); 114 } 115 } 116 117 void Validator::valueTreeChildRemoved(juce::ValueTree& parent, juce::ValueTree& child, int indexOfChild) 118 { 119 if(parent.getType() == IDs::hierarchyMapping) 120 { 121 const auto importDestinationType = juce::VariantConverter<Wwise::ObjectType>::fromVar(applicationState[IDs::importDestinationType]); 122 123 validateHierarchyMapping(importDestinationType, parent); 124 } 125 } 126 127 void Validator::valueTreeChildOrderChanged(juce::ValueTree& parent, int oldIndex, int newIndex) 128 { 129 if(parent.getType() == IDs::hierarchyMapping) 130 { 131 const auto importDestinationType = juce::VariantConverter<Wwise::ObjectType>::fromVar(applicationState[IDs::importDestinationType]); 132 133 validateHierarchyMapping(importDestinationType, parent); 134 } 135 } 136 137 bool Validator::validateOriginalsSubfolder(const juce::String& originalsFolder, const juce::String& languageSubfolder, const juce::String& originalsSubfolder) 138 { 139 if(originalsFolder.isEmpty() || originalsSubfolder.isEmpty()) 140 return true; 141 142 auto originalsFolderWithLanguageSubfolder = juce::File(originalsFolder).getChildFile(languageSubfolder); 143 144 auto originalsSubfolderAbsolutePath = originalsFolderWithLanguageSubfolder.getChildFile(originalsSubfolder); 145 146 return originalsSubfolderAbsolutePath.isAChildOf(originalsFolderWithLanguageSubfolder); 147 } 148 149 bool Validator::validateImportDestination(const juce::String& importDestination) 150 { 151 using namespace Wwise; 152 153 static const juce::String pathPrefix = "\\Actor-Mixer Hierarchy"; 154 155 auto allowedPathPrefix = importDestination.startsWith(pathPrefix); 156 157 return importDestination.isNotEmpty() && !importDestination.endsWith("\\") && allowedPathPrefix; 158 } 159 160 void Validator::validatePropertyTemplatePath(juce::ValueTree hierarchyMappingNode) 161 { 162 using namespace Wwise; 163 164 const juce::String propertyTemplatePath = hierarchyMappingNode[IDs::propertyTemplatePath]; 165 const auto propertyTemplatePathType = juce::VariantConverter<Wwise::ObjectType>::fromVar(hierarchyMappingNode[IDs::propertyTemplatePathType]); 166 167 static const std::initializer_list<ObjectType> allowedObjectTypes = {ObjectType::WorkUnit, 168 ObjectType::RandomContainer, ObjectType::BlendContainer, ObjectType::ActorMixer, ObjectType::SwitchContainer, 169 ObjectType::Sound}; 170 171 static const juce::String pathPrefix = "\\Actor-Mixer Hierarchy"; 172 173 auto allowedType = std::find(allowedObjectTypes.begin(), allowedObjectTypes.end(), 174 propertyTemplatePathType) != allowedObjectTypes.end() || 175 propertyTemplatePathType == ObjectType::Unknown; 176 177 auto allowedPathPrefix = propertyTemplatePath.startsWith(pathPrefix); 178 179 bool isValid = propertyTemplatePath.isEmpty() || allowedType && allowedPathPrefix; 180 juce::String errorMessage = isValid ? "" : "Invalid property template path"; 181 182 hierarchyMappingNode.setPropertyExcludingListener(this, IDs::propertyTemplatePathValid, isValid, nullptr); 183 hierarchyMappingNode.setPropertyExcludingListener(this, IDs::propertyTemplatePathErrorMessage, errorMessage, nullptr); 184 } 185 186 void Validator::validateObjectName(juce::ValueTree hierarchyMappingNode) 187 { 188 juce::String objectName = hierarchyMappingNode[IDs::objectName]; 189 190 bool isValid = objectName.isNotEmpty(); 191 juce::String errorMessage = isValid ? "" : "Object name cannot be empty"; 192 193 hierarchyMappingNode.setPropertyExcludingListener(this, IDs::objectNameValid, isValid, nullptr); 194 hierarchyMappingNode.setPropertyExcludingListener(this, IDs::objectNameErrorMessage, errorMessage, nullptr); 195 } 196 197 void Validator::validateHierarchyMapping(Wwise::ObjectType importDestinationType, juce::ValueTree hierarchyMapping) 198 { 199 // To properly validate the hierarchy mapping, we must include the import destination 200 std::vector<Wwise::ObjectType> hierarchyTypes{importDestinationType}; 201 for(int i = 0; i < hierarchyMapping.getNumChildren(); ++i) 202 { 203 const auto hierarchyMappingNode = hierarchyMapping.getChild(i); 204 hierarchyTypes.emplace_back(juce::VariantConverter<Wwise::ObjectType>::fromVar(hierarchyMappingNode[IDs::objectType])); 205 } 206 207 for(std::size_t i = 1; i < hierarchyTypes.size(); ++i) 208 { 209 const auto child = hierarchyTypes[i]; 210 const auto parent = hierarchyTypes[i - 1]; 211 212 bool isValid = true; 213 juce::String errorMessage; 214 215 // Last item must be SoundSFX, report this error above any others 216 if(i == hierarchyTypes.size() - 1 && child != Wwise::ObjectType::SoundSFX && child != Wwise::ObjectType::SoundVoice) 217 { 218 isValid = false; 219 errorMessage << "Last item must be of type 'SoundSFX' or 'Sound Voice'"; 220 } 221 else if(parent != Wwise::ObjectType::Unknown && 222 child != Wwise::ObjectType::Unknown && 223 !WwiseHelper::validateObjectTypeParentChildRelationShip(parent, child)) 224 { 225 isValid = false; 226 errorMessage << "'" << WwiseHelper::objectTypeToReadableString(child) << "' cannot be a child of '" << WwiseHelper::objectTypeToReadableString(parent) << "'"; 227 } 228 229 auto hierarchyMappingNode = hierarchyMapping.getChild(i - 1); // Index is off by 1 due to the importDestination 230 hierarchyMappingNode.setPropertyExcludingListener(this, IDs::objectTypeValid, isValid, nullptr); 231 hierarchyMappingNode.setPropertyExcludingListener(this, IDs::objectTypeErrorMessage, errorMessage, nullptr); 232 } 233 } 234 } // namespace AK::WwiseTransfer::ApplicationState