File: ActionDefinitionManager.cpp

package info (click to toggle)
freespace2 24.2.0%2Brepack-3
  • links: PTS, VCS
  • area: non-free
  • in suites: forky, sid
  • size: 43,740 kB
  • sloc: cpp: 595,005; ansic: 21,741; python: 1,174; sh: 457; makefile: 243; xml: 181
file content (145 lines) | stat: -rw-r--r-- 5,693 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145

#include "ActionDefinitionManager.h"

#include "BuiltinActionDefinition.h"

#include "actions/types/MoveToSubmodel.h"
#include "actions/types/ParticleEffectAction.h"
#include "actions/types/PlaySoundAction.h"
#include "actions/types/SetDirectionAction.h"
#include "actions/types/SetPositionAction.h"
#include "actions/types/WaitAction.h"
#include "utils/join_string.h"

namespace actions {
namespace {

void parseActionParameters(const SCP_vector<ActionParameter>& params,
	SCP_unordered_map<SCP_string, expression::ActionExpression>& parameterExpressions,
	const expression::ParseContext& context)
{
	// We do not impose a set order of the parameters so we need to do something similar to the outer loop and
	// check for every possibility in a loop.
	for (size_t i = 0; i < params.size(); ++i) {
		// We might be doing some empty loops here in case of errors but there is a bound so this will finish
		// eventually
		for (const auto& parameter : params) {
			// Do not check for parameters which we already found
			if (parameterExpressions.find(parameter.name) != parameterExpressions.end()) {
				continue;
			}

			const auto paramString = "+" + parameter.name + ":";

			if (!optional_string(paramString.c_str())) {
				continue;
			}

			// We found our parameter!
			parameterExpressions[parameter.name] =
				expression::ActionExpression::parseFromTable(parameter.type, context);
			break;
		}
	}
}

} // namespace

const ActionDefinitionManager& ActionDefinitionManager::instance()
{
	static const ActionDefinitionManager manager;
	return manager;
}
void ActionDefinitionManager::addDefinition(std::unique_ptr<ActionDefinition> def)
{
	m_definitions.push_back(std::move(def));
}
std::unique_ptr<Action> ActionDefinitionManager::parseAction(const flagset<ProgramContextFlags>& context_flags,
	const expression::ParseContext& context) const
{
	// We search through all definitions for every time we want to check for an action. This is not the most efficient
	// way to handle this since it results in an O(n^2) runtime. A better alternative would be to either construct a
	// regex with all the accepted actions as alternatives (although runtime of this is also doubtful) or peek at the
	// next token, extract the relevant part (The stuff between "+" and ":") and then look that up in a map. Then we
	// would get down to O(n).
	// Since this only happens at parse time, this is good enough for now.
	for (const auto& actionDef : m_definitions) {
		const auto parseString = "+" + actionDef->getName() + ":";

		if (!optional_string(parseString.c_str())) {
			continue;
		}

		SCP_unordered_map<SCP_string, expression::ActionExpression> parameterExpressions;
		const auto& params = actionDef->getParameters();

		if (params.size() == 1) {
			// Single parameters are expected to be placed directly after the action name
			parameterExpressions[params.front().name] =
				expression::ActionExpression::parseFromTable(params.front().type, context);
		} else {
			parseActionParameters(params, parameterExpressions, context);

			if (parameterExpressions.size() != params.size()) {
				SCP_vector<ActionParameter> missingParameters;
				std::copy_if(params.cbegin(),
					params.cend(),
					std::back_inserter(missingParameters),
					[&parameterExpressions](const ActionParameter& param) {
						const auto iter = parameterExpressions.find(param.name);
						return iter == parameterExpressions.cend();
					});

				SCP_vector<SCP_string> missingParameterNames;
				std::transform(missingParameters.cbegin(),
					missingParameters.cend(),
					std::back_inserter(missingParameterNames),
					[](const ActionParameter& param) { return "+" + param.name; });

				const auto namesList = util::join_container(missingParameterNames, ", ");

				error_display(0, "Missing parameters for action '%s': %s", parseString.c_str(), namesList.c_str());

				// Fix up the parameter list so that we can continue parsing
				for (const auto& missingParam : missingParameters) {
					parameterExpressions[missingParam.name] = expression::ActionExpression();
				}
			}
		}

		auto actionInstance = actionDef->createAction(parameterExpressions);

		const auto requiredFlags = actionDef->getRequiredContext();

		if ((context_flags & requiredFlags) != requiredFlags) {
			// We are missing at least one context flag so this action is not valid here. We still return the action
			// here though since that is the only way we can keep parsing the table without throwing random error
			// messages at the user. However, this will trigger an Assertion (in debug) or crashes when the action is
			// actually run later.
			error_display(0,
				"The action %s is not available in the current context. This will lead to an Assertion failure when "
				"this action is triggered.",
				parseString.c_str());
		}

		return actionInstance;
	}

	return std::unique_ptr<Action>();
}
ActionDefinitionManager::ActionDefinitionManager()
{
	addDefinition(
		std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::MoveToSubmodel>("Move To Subobject")));
	addDefinition(std::unique_ptr<ActionDefinition>(
		new BuiltinActionDefinition<types::ParticleEffectAction>("Start Particle Effect")));
	addDefinition(
		std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::PlaySoundAction>("Play 3D Sound")));
	addDefinition(
		std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::SetDirectionAction>("Set Direction")));
	addDefinition(
		std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::SetPositionAction>("Set Position")));
	addDefinition(std::unique_ptr<ActionDefinition>(new BuiltinActionDefinition<types::WaitAction>("Wait")));
}

} // namespace actions