Skip to content

Commit c9cc432

Browse files
NextAction refactoring to eliminate sentinel arrays and pointers (mod-playerbots#1923)
<!-- > [!WARNING] > **This is a DRAFT PR.** > The structure is not definitive. The code might not be optimised yet. It might not even start nor compile yet. > **Don't panic. ✋ It's going to be ok. 👌 We can make modifications, we can fix things.** 😁 --> # Description This PR aims to refactor the NextAction declaration to achieve two goals: ## Eliminate C-style sentinel arrays Currently, a double pointer (`NextAction**`) approach is being used. This an old pre-C++11 (< 2011) trick before `std::vector<>` became a thing. This approach is painful for developers because they constantly need to declare their `NextAction` arrays as: ```cpp NextAction::array(0, new NextAction("foo", 1.0f), nullptr) ``` Instead of: ```cpp { new NextAction("foo", 1.0f) } ``` The first argument of `NextAction::array` is actually a hack. It is used to have a named argument so `va_args` can find the remaining arguments. It is set to 0 everywhere but in fact does nothing. This is very confusing to people unfamiliar with this antiquated syntax. The last argument `nullptr` is what we call a sentinel. It's a `nullptr` because `va_args` is looking for a `nullptr` to stop iterating. It's also a hack and also leads to confusion. ## Eliminate unnecessary pointers for `NextAction` Pointers can be used for several reasons, to cite a few: - Indicate strong, absolute identity. - Provide strong but transferable ownership (unlike references). - When a null value is acceptable (`nullptr`). - When copy is expensive. `NextAction` meets none of these criteria: - It has no identity because it is purely behavioural. - It is never owned by anything as it is passed around and never fetched from a registry. - The only situations where it can be `nullptr` are errors that should in fact throw an `std::invalid_argument` instead. - They are extremely small objects that embark a single `std::string` and a single `float`. Pointers should be avoided when not strictly necessary because they can quickly lead to undefined behaviour due to unhandled `nullptr` situations. They also make the syntax heavier due to the necessity to constantly check for `nullptr`. Finally, they aren't even good for performance in that situation because shifting a pointer so many times is likely more expensive than copying such a trivial object. # End goal The end goal is to declare `NextAction` arrays this way: ```cpp { NextAction("foo", 1.0f) } ``` > [!NOTE] > Additional note: `NextAction` is nothing but a hacky proxy to an `Action` constructor. This should eventually be reworked to use handles instead of strings. This would make copying `NextAction` even cheaper and remove the need for the extremely heavy stringly typed current approach. Stringly typed entities are a known anti-pattern so we need to move on from those.
1 parent b13fb7d commit c9cc432

197 files changed

Lines changed: 6790 additions & 4049 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/TravelMgr.cpp

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3409,13 +3409,14 @@ void TravelMgr::LoadQuestTravelTable()
34093409
{
34103410
Strategy* strat = con->GetStrategy(stratName);
34113411

3412-
if (strat->getDefaultActions())
3413-
for (uint32 i = 0; i < NextAction::size(strat->getDefaultActions()); i++)
3414-
{
3415-
NextAction* nextAction = strat->getDefaultActions()[i];
3412+
const std::vector<NextAction> defaultActions = strat->getDefaultActions();
34163413

3414+
if (defaultActions.size() > 0)
3415+
{
3416+
for (NextAction nextAction : defaultActions)
3417+
{
34173418
std::ostringstream aout;
3418-
aout << nextAction->getRelevance() << "," << nextAction->getName()
3419+
aout << nextAction.getRelevance() << "," << nextAction.getName()
34193420
<< ",,S:" << stratName;
34203421

34213422
if (actions.find(aout.str().c_str()) != actions.end())
@@ -3427,27 +3428,24 @@ void TravelMgr::LoadQuestTravelTable()
34273428

34283429
actions.insert_or_assign(aout.str().c_str(), classSpecLevel);
34293430
}
3431+
}
34303432

34313433
std::vector<TriggerNode*> triggers;
34323434
strat->InitTriggers(triggers);
3433-
for (auto& triggerNode : triggers)
3434-
{
3435-
// out << " TN:" << triggerNode->getName();
34363435

3436+
for (TriggerNode*& triggerNode : triggers)
3437+
{
34373438
if (Trigger* trigger = con->GetTrigger(triggerNode->getName()))
34383439
{
34393440
triggerNode->setTrigger(trigger);
34403441

3441-
NextAction** nextActions = triggerNode->getHandlers();
3442+
std::vector<NextAction> nextActions = triggerNode->getHandlers();
34423443

3443-
for (uint32 i = 0; i < NextAction::size(nextActions); i++)
3444+
// for (uint32_t i = 0; i < nextActions.size(); ++i)
3445+
for (NextAction nextAction : nextActions)
34443446
{
3445-
NextAction* nextAction = nextActions[i];
3446-
// out << " A:" << nextAction->getName() << "(" <<
3447-
// nextAction->getRelevance() << ")";
3448-
34493447
std::ostringstream aout;
3450-
aout << nextAction->getRelevance() << "," << nextAction->getName()
3448+
aout << nextAction.getRelevance() << "," << nextAction.getName()
34513449
<< "," << triggerNode->getName() << "," << stratName;
34523450

34533451
if (actions.find(aout.str().c_str()) != actions.end())

src/strategy/Action.cpp

Lines changed: 1 addition & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -8,90 +8,6 @@
88
#include "Playerbots.h"
99
#include "Timer.h"
1010

11-
uint32 NextAction::size(NextAction** actions)
12-
{
13-
if (!actions)
14-
return 0;
15-
16-
uint32 size = 0;
17-
for (size = 0; actions[size];)
18-
++size;
19-
20-
return size;
21-
}
22-
23-
NextAction** NextAction::clone(NextAction** actions)
24-
{
25-
if (!actions)
26-
return nullptr;
27-
28-
uint32 size = NextAction::size(actions);
29-
30-
NextAction** res = new NextAction*[size + 1];
31-
for (uint32 i = 0; i < size; i++)
32-
res[i] = new NextAction(*actions[i]);
33-
34-
res[size] = nullptr;
35-
36-
return res;
37-
}
38-
39-
NextAction** NextAction::merge(NextAction** left, NextAction** right)
40-
{
41-
uint32 leftSize = NextAction::size(left);
42-
uint32 rightSize = NextAction::size(right);
43-
44-
NextAction** res = new NextAction*[leftSize + rightSize + 1];
45-
46-
for (uint32 i = 0; i < leftSize; i++)
47-
res[i] = new NextAction(*left[i]);
48-
49-
for (uint32 i = 0; i < rightSize; i++)
50-
res[leftSize + i] = new NextAction(*right[i]);
51-
52-
res[leftSize + rightSize] = nullptr;
53-
54-
NextAction::destroy(left);
55-
NextAction::destroy(right);
56-
57-
return res;
58-
}
59-
60-
NextAction** NextAction::array(uint32 nil, ...)
61-
{
62-
va_list vl;
63-
va_start(vl, nil);
64-
65-
uint32 size = 0;
66-
NextAction* cur = nullptr;
67-
do
68-
{
69-
cur = va_arg(vl, NextAction*);
70-
++size;
71-
} while (cur);
72-
73-
va_end(vl);
74-
75-
NextAction** res = new NextAction*[size];
76-
va_start(vl, nil);
77-
for (uint32 i = 0; i < size; i++)
78-
res[i] = va_arg(vl, NextAction*);
79-
va_end(vl);
80-
81-
return res;
82-
}
83-
84-
void NextAction::destroy(NextAction** actions)
85-
{
86-
if (!actions)
87-
return;
88-
89-
for (uint32 i = 0; actions[i]; i++)
90-
delete actions[i];
91-
92-
delete[] actions;
93-
}
94-
9511
Value<Unit*>* Action::GetTargetValue() { return context->GetValue<Unit*>(GetTargetName()); }
9612

9713
Unit* Action::GetTarget() { return GetTargetValue()->Get(); }
@@ -101,4 +17,4 @@ ActionBasket::ActionBasket(ActionNode* action, float relevance, bool skipPrerequ
10117
{
10218
}
10319

104-
bool ActionBasket::isExpired(uint32 msecs) { return getMSTime() - created >= msecs; }
20+
bool ActionBasket::isExpired(uint32_t msecs) { return getMSTime() - created >= msecs; }

src/strategy/Action.h

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
* and/or modify it under version 3 of the License, or (at your option), any later version.
44
*/
55

6-
#ifndef _PLAYERBOT_ACTION_H
7-
#define _PLAYERBOT_ACTION_H
6+
#pragma once
87

98
#include "AiObject.h"
109
#include "Common.h"
@@ -24,15 +23,26 @@ class NextAction
2423
std::string const getName() { return name; }
2524
float getRelevance() { return relevance; }
2625

27-
static uint32 size(NextAction** actions);
28-
static NextAction** clone(NextAction** actions);
29-
static NextAction** merge(NextAction** what, NextAction** with);
30-
static NextAction** array(uint32 nil, ...);
31-
static void destroy(NextAction** actions);
26+
static std::vector<NextAction> merge(std::vector<NextAction> const& what, std::vector<NextAction> const& with)
27+
{
28+
std::vector<NextAction> result = {};
29+
30+
for (NextAction const& action : what)
31+
{
32+
result.push_back(action);
33+
}
34+
35+
for (NextAction const& action : with)
36+
{
37+
result.push_back(action);
38+
}
39+
40+
return result;
41+
};
3242

3343
private:
3444
float relevance;
35-
std::string const name;
45+
std::string name;
3646
};
3747

3848
class Action : public AiNamedObject
@@ -52,9 +62,9 @@ class Action : public AiNamedObject
5262
virtual bool Execute([[maybe_unused]] Event event) { return true; }
5363
virtual bool isPossible() { return true; }
5464
virtual bool isUseful() { return true; }
55-
virtual NextAction** getPrerequisites() { return nullptr; }
56-
virtual NextAction** getAlternatives() { return nullptr; }
57-
virtual NextAction** getContinuers() { return nullptr; }
65+
virtual std::vector<NextAction> getPrerequisites() { return {}; }
66+
virtual std::vector<NextAction> getAlternatives() { return {}; }
67+
virtual std::vector<NextAction> getContinuers() { return {}; }
5868
virtual ActionThreatType getThreatType() { return ActionThreatType::None; }
5969
void Update() {}
6070
void Reset() {}
@@ -73,39 +83,44 @@ class Action : public AiNamedObject
7383
class ActionNode
7484
{
7585
public:
76-
ActionNode(std::string const name, NextAction** prerequisites = nullptr, NextAction** alternatives = nullptr,
77-
NextAction** continuers = nullptr)
78-
: name(name), action(nullptr), continuers(continuers), alternatives(alternatives), prerequisites(prerequisites)
79-
{
80-
} // reorder arguments - whipowill
81-
82-
virtual ~ActionNode()
83-
{
84-
NextAction::destroy(prerequisites);
85-
NextAction::destroy(alternatives);
86-
NextAction::destroy(continuers);
87-
}
86+
ActionNode(
87+
std::string name,
88+
std::vector<NextAction> prerequisites = {},
89+
std::vector<NextAction> alternatives = {},
90+
std::vector<NextAction> continuers = {}
91+
) :
92+
name(std::move(name)),
93+
action(nullptr),
94+
continuers(continuers),
95+
alternatives(alternatives),
96+
prerequisites(prerequisites)
97+
{}
98+
99+
virtual ~ActionNode() = default;
88100

89101
Action* getAction() { return action; }
90102
void setAction(Action* action) { this->action = action; }
91-
std::string const getName() { return name; }
103+
const std::string getName() { return name; }
92104

93-
NextAction** getContinuers() { return NextAction::merge(NextAction::clone(continuers), action->getContinuers()); }
94-
NextAction** getAlternatives()
105+
std::vector<NextAction> getContinuers()
95106
{
96-
return NextAction::merge(NextAction::clone(alternatives), action->getAlternatives());
107+
return NextAction::merge(this->continuers, action->getContinuers());
97108
}
98-
NextAction** getPrerequisites()
109+
std::vector<NextAction> getAlternatives()
99110
{
100-
return NextAction::merge(NextAction::clone(prerequisites), action->getPrerequisites());
111+
return NextAction::merge(this->alternatives, action->getAlternatives());
112+
}
113+
std::vector<NextAction> getPrerequisites()
114+
{
115+
return NextAction::merge(this->prerequisites, action->getPrerequisites());
101116
}
102117

103118
private:
104-
std::string const name;
119+
const std::string name;
105120
Action* action;
106-
NextAction** continuers;
107-
NextAction** alternatives;
108-
NextAction** prerequisites;
121+
std::vector<NextAction> continuers;
122+
std::vector<NextAction> alternatives;
123+
std::vector<NextAction> prerequisites;
109124
};
110125

111126
class ActionBasket
@@ -121,14 +136,12 @@ class ActionBasket
121136
bool isSkipPrerequisites() { return skipPrerequisites; }
122137
void AmendRelevance(float k) { relevance *= k; }
123138
void setRelevance(float relevance) { this->relevance = relevance; }
124-
bool isExpired(uint32 msecs);
139+
bool isExpired(uint32_t msecs);
125140

126141
private:
127142
ActionNode* action;
128143
float relevance;
129144
bool skipPrerequisites;
130145
Event event;
131-
uint32 created;
146+
uint32_t created;
132147
};
133-
134-
#endif

0 commit comments

Comments
 (0)