fsd_behavior_loader.py (7907B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\behaviors\loaders\fsd_behavior_loader.py 2 from collections import namedtuple 3 from behaviors import BehaviortreeStorage 4 from behaviors.behaviortree import BehaviorTree 5 from behaviors.loaders import BehaviorDoesNotExistError, IncompatibleScopeTypeError, TaskAttributeMissingError 6 from behaviors.registry import get_task_registry 7 from ccpProfile import TimedFunction 8 from functoolsext import lru_cache 9 import logging 10 logger = logging.getLogger(__name__) 11 EMPTY_LIST = [] 12 13 class CircularReferenceError(Exception): 14 pass 15 16 17 def get_parameter_name(attribute_name): 18 return attribute_name.split('.')[1] 19 20 21 def get_behavior_tree_data(behavior_id, scope_type): 22 storage = BehaviortreeStorage() 23 try: 24 behavior_tree_data = storage.get(behavior_id) 25 except ValueError: 26 raise BehaviorDoesNotExistError() 27 28 if behavior_tree_data is None: 29 raise BehaviorDoesNotExistError() 30 if scope_type and scope_type != behavior_tree_data['scopeType']: 31 raise IncompatibleScopeTypeError() 32 return behavior_tree_data 33 34 35 def get_id_and_names_by_scope(scope_type): 36 storage = BehaviortreeStorage() 37 ids_and_names = [] 38 for behavior_id in storage: 39 behavior_tree_data = storage[behavior_id] 40 if behavior_tree_data.get('scopeType', None) != scope_type: 41 continue 42 ids_and_names.append((str(behavior_id), '[FSD] %s (%d)' % (behavior_tree_data.get('name', '<Unnamed FSD Behavior>'), behavior_id))) 43 44 return ids_and_names 45 46 47 def get_attributes_with_overrides(node, overrides_by_uuid): 48 task_uuid = node.get('uuid', None) 49 overrides = overrides_by_uuid.get(task_uuid, {}) 50 attributes = node['attributes'].copy() 51 for attribute_name, attribute_value in overrides.iteritems(): 52 attributes[attribute_name] = attribute_value 53 54 return attributes 55 56 57 def get_attribute_overrides_by_uuid(parameter_overrides, parameters_values): 58 overrides_by_uuid = {} 59 for parameter in parameter_overrides: 60 for override in parameter['attributeOverrideList']: 61 task_uuid = override['taskUuid'] 62 if task_uuid not in overrides_by_uuid: 63 overrides_by_uuid[task_uuid] = {} 64 attribute_name = override['attributeName'] 65 parameter_name = parameter['parameterName'] 66 if parameter_name in parameters_values: 67 overrides_by_uuid[task_uuid][attribute_name] = parameters_values[parameter_name] 68 69 return overrides_by_uuid 70 71 72 def get_parameter_values_with_overrides(parameters, overrides): 73 if not overrides: 74 return parameters 75 updated_parameters = parameters.copy() 76 logger.debug('applying overrides to %s with overrides %s', parameters, overrides) 77 for parameter_name, parameter_value in overrides.iteritems(): 78 if '.' not in parameter_name: 79 continue 80 updated_parameters[get_parameter_name(parameter_name)] = parameter_value 81 82 return updated_parameters 83 84 85 class FsdBehaviorLoader(object): 86 87 def __init__(self): 88 self.registry = get_task_registry() 89 self.attribute_tuple_by_task_class = {} 90 self._subtree_stack = [] 91 92 @TimedFunction('behaviors::loaders::FsdBehaviorLoader::load') 93 def load(self, behavior_id, scope_type = None): 94 logger.debug('loading behavior tree %s with with scope type %s', behavior_id, scope_type) 95 behavior_tree_data = get_behavior_tree_data(behavior_id, scope_type) 96 root_node = behavior_tree_data['root'] 97 tree = BehaviorTree(behaviorId=behavior_id, scopeType=scope_type) 98 self._subtree_stack = [] 99 root_task = self._instantiate_tree(root_node, {}) 100 tree.StartRootTask(root_task) 101 return tree 102 103 @TimedFunction('behaviors::loaders::FsdBehaviorLoader::load_reference_subtree') 104 def load_reference_subtree(self, behavior_id, scope_type, parameters_values): 105 if behavior_id in self._subtree_stack: 106 raise CircularReferenceError('Detected circular references while loading subtree %s. Stack was %s' % (behavior_id, self._subtree_stack)) 107 self._subtree_stack.append(behavior_id) 108 logger.debug('loading subtree %s with parameter values %s', behavior_id, parameters_values) 109 behavior_tree_data = get_behavior_tree_data(behavior_id, scope_type) 110 root_node = behavior_tree_data['root'] 111 parameter_overrides = behavior_tree_data.get('parameters', []) 112 overrides_by_uuid = get_attribute_overrides_by_uuid(parameter_overrides, parameters_values) 113 root_task = self._instantiate_tree(root_node, overrides_by_uuid) 114 self._subtree_stack.pop() 115 return root_task 116 117 @TimedFunction('behaviors::loaders::FsdBehaviorLoader::_instantiate_tree') 118 def _instantiate_tree(self, node, overrides_by_uuid): 119 task_name = node['taskClass'] 120 task_class = self.get_task_class(task_name) 121 attributes = self._get_task_attributes(node, task_name, overrides_by_uuid) 122 task = task_class(attributes) 123 if task_name.endswith('ReferenceSubtree'): 124 subtree_id = attributes.behaviorTreeId 125 parameter_values = get_parameter_values_with_overrides(attributes.parameters, overrides_by_uuid.get(node['uuid'])) 126 task = self.load_reference_subtree(subtree_id, None, parameter_values) 127 else: 128 self._instantiate_child_tasks(node, task, overrides_by_uuid) 129 return task 130 131 def _instantiate_child_tasks(self, node, task, overrides_by_uuid): 132 for child_node in node.get('subTasks', EMPTY_LIST): 133 child_task = self._instantiate_tree(child_node, overrides_by_uuid) 134 task.AddSubTask(child_task) 135 136 def _get_task_attributes(self, node, task_name, overrides_by_uuid): 137 attribute_wrapper = self._get_attribute_wrapper(task_name) 138 node_attributes = get_attributes_with_overrides(node, overrides_by_uuid) 139 try: 140 attributes = attribute_wrapper(**self._get_converted_attributes(task_name, attribute_wrapper, node_attributes)) 141 except: 142 raise 143 144 return attributes 145 146 @lru_cache(maxsize=None) 147 def get_task_class(self, task_name): 148 task_class = self.registry.get_class_from_name(task_name) 149 return task_class 150 151 @lru_cache(maxsize=None) 152 def _get_attribute_wrapper(self, task_name): 153 task_attributes = self.registry.get_class_attributes(task_name) 154 attribute_tuple = namedtuple('%s_AttributeTuple' % task_name.split('.')[-1], ['name'] + task_attributes.keys()) 155 return attribute_tuple 156 157 def get_id_and_names_by_scope(self, scope_type): 158 return get_id_and_names_by_scope(scope_type) 159 160 def _get_converted_attributes(self, task_name, attribute_wrapper, node_attributes): 161 attributes = {} 162 for name in attribute_wrapper._fields: 163 if name == 'name': 164 value = node_attributes.get(name, '') 165 elif name in node_attributes: 166 value = self._convert_fsd_value(task_name, name, node_attributes[name]) 167 elif self._is_optional(task_name, name): 168 value = None 169 else: 170 raise TaskAttributeMissingError() 171 attributes[name] = value 172 173 return attributes 174 175 def _convert_fsd_value(self, task_name, name, value): 176 task_attributes = self.registry.get_class_attributes(task_name) 177 attribute = task_attributes[name] 178 try: 179 return attribute.convert_fsd_to_attribute(value) 180 except: 181 logger.exception('task %s failed to convert fsd attribute %s with value %s' % (task_name, name, value)) 182 raise 183 184 def _is_optional(self, task_name, name): 185 task_attributes = self.registry.get_class_attributes(task_name) 186 attribute = task_attributes[name] 187 return attribute.is_optional