Skip to content

New REMOVE_RANGE command for range removal #2156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3fd7f54
Add META modifier to INSERT cmd. With it will do range deletion, inst…
saurtron Apr 8, 2025
03f9ced
Check firstIndex isn't negative.
saurtron Apr 8, 2025
019acf6
Don't META if ALT is present too for now.
saurtron Apr 8, 2025
4d90e61
Run UnitCmdDone callin for finished REMOVE commands.
saurtron Apr 8, 2025
3c4156c
Implement meta+alt mode (range where start and end provided as tags).
saurtron Apr 8, 2025
9fa89e6
Make sure lastIndex is greater or equal to firstIndex.
saurtron Apr 8, 2025
a747e8c
Move foundStart, foundEnd a bit up.
saurtron Apr 8, 2025
82762a3
No need to reset iterator here.
saurtron Apr 8, 2025
5dfa83a
Factor out FindTagIndex from GetRemoveLimitsFromOptions.
saurtron Apr 9, 2025
133a210
Use 1 based start and end indexes.
saurtron Apr 9, 2025
14a3367
Use implicit optional notation and -- instead of -=.
saurtron Apr 9, 2025
d5eeae2
No need to use 'active' here.
saurtron Apr 9, 2025
bc5b37c
some spacing.
saurtron Apr 9, 2025
c855fcd
refresh iterator since it may be invalidated.
saurtron Apr 9, 2025
cc376a3
No need for utility header.
saurtron Apr 9, 2025
2046214
use >= for consistency
saurtron Apr 9, 2025
ebf7633
make firstTagIndex const
saurtron Apr 9, 2025
da2e251
make lastTagIndex const
saurtron Apr 9, 2025
b1a84c0
spacing after while
saurtron Apr 9, 2025
6d1a259
just check if queue is empty, checking GetNumParams() < 0 doesn't mak…
saurtron Apr 9, 2025
818726d
Index parsing fixes.
saurtron Apr 10, 2025
286ec98
Also run CmdDone when returning early.
saurtron Apr 10, 2025
d6b052f
Never accept lastIndex smaller than firstIndex, or firstIndex greater…
saurtron Apr 11, 2025
b2f405f
Improve comment.
saurtron Apr 11, 2025
72aaf17
Move removeByID down, since it's not used in the top loop.
saurtron Apr 11, 2025
9fa74af
Remove unused braces.
saurtron Apr 11, 2025
07d06f7
Also move active down, since it's not used in the first loop.
saurtron Apr 11, 2025
9cc92ba
Fix top of loop comments and move active.
saurtron Apr 11, 2025
084737b
Move the range deletion remove to new command CMD_REMOVE_RANGE.
saurtron Apr 12, 2025
8786b35
Remove UnitCmdDone calling from REMOVE.
saurtron Apr 12, 2025
e3a93a8
Restore previous ExecuteRemove code.
saurtron Apr 12, 2025
0b1943a
Fix comment about ALT behaviour on ExecuteRemoveRange.
saurtron Apr 14, 2025
2e1e979
Remove tag option from REMOVE_RANGE.
saurtron Apr 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 88 additions & 1 deletion rts/Sim/Units/CommandAI/CommandAI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,52 @@ void CCommandAI::ExecuteInsert(const Command& c, bool fromSynced)
SlowUpdate();
}

const std::optional<int> CCommandAI::FindTagIndex(const CCommandQueue& queue, unsigned int tag) const
{
int i = 0;
for (const auto& qc: queue) {
if (qc.GetTag() == tag)
return i;
++i;
}
return std::nullopt;
}


const std::optional<std::pair<int, int>> CCommandAI::GetRemoveLimitsFromOptions(const Command& c, const CCommandQueue& queue) const
{
int firstIndex = 0;
int lastIndex = queue.size() - 1;

if (c.GetOpts() & ALT_KEY) {
if (c.GetNumParams() >= 1) {
firstIndex = std::max<int>(c.GetParam(0) - 1, 0);
}
if (c.GetNumParams() >= 2)
lastIndex = std::clamp<int>(c.GetParam(1) - 1, 0, queue.size() - 1);
if (lastIndex < firstIndex || firstIndex >= queue.size())
return std::nullopt;
} else if (c.GetNumParams() >= 1) {
const auto firstTagIndex = FindTagIndex(queue, c.GetParam(0));
if (!firstTagIndex)
return std::nullopt;

if (c.GetNumParams() >= 2) {
const auto lastTagIndex = FindTagIndex(queue, c.GetParam(1));
if (!lastTagIndex)
return std::nullopt;
lastIndex = *lastTagIndex;
}

firstIndex = *firstTagIndex;

if (lastIndex < firstIndex)
std::swap(lastIndex, firstIndex);
}

return std::make_pair(firstIndex, lastIndex);
}


void CCommandAI::ExecuteRemove(const Command& c)
{
Expand All @@ -1238,11 +1284,51 @@ void CCommandAI::ExecuteRemove(const Command& c)
}
}

if ((c.GetNumParams() <= 0) || (queue->size() <= 0))
if (queue->empty())
return;

repeatOrders = false;

if (c.GetOpts() & META_KEY) {
const auto limits = GetRemoveLimitsFromOptions(c, *queue);
if (!limits)
return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!limits)
return;
if (!limits) {
eventHandler.UnitCmdDone(owner, c);
return;
}

Copy link
Collaborator

@sprunk sprunk Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you skip this event on purpose so that "invalid" orders are dropped silently then that's also fine, but if that is the case then I would still want the case where firstIndex >= queue.size() to be considered valid. This is because you can use the remove(5, math.huge) idiom to cull the queue to 5 orders regardless of how many there are in the queue at the moment, and it's still a "success" if there was nothing to do.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, unsure what to do there... could maybe run it only in that case, or either maybe run the callin in all return early cases but that'd be a bit cumbersome. Even if considered "valid", since it doesn't do anything, do you really think it needs the UnitCmdDone callin there? Not sure it's really needed if nothing really happened, unless we want to send it unconditionally so the sender can always do smth after this is resolved one way or another...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if it produced an event because it feels arbitrary that a valid command would just silently disappear with no trace if it just so happened its conditions were already fulfilled. Perhaps check what happens if you give a move command onto the unit's current position, or a "set firestate to holdfire" command if it's already holdfire etc. (I don't know what happens but I would assume they both produce an event), and then make remove behave consistently with that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tricky since state commands never send any CmdDone, and move probably always sends since it wouldn't consider same position as a fail condition.

Maybe ATTACK is a good example, it does send CmdDone through FinishCommand when returning early because of any bad parameters.

On the other hand, INSERT and REMOVE didn't generally send any callins, there could be some cases where queue manipulation led to other commands ending and sending CmdDone themselves.

Anyways I'm leaning more towards adding CmdDone in all early returns too, just to be generally consistent inside CMD_REMOVE, like attack does. I think not having something here was probably a problem/bug, and regarding INSERT.

Will have to check how lua finds out about the queue changing, but there doesn't seem to be a good mechanism for the general case. I guess it finds out because it knows the command is about to be given, but not because the command was already processed.

const auto [firstIndex, lastIndex] = *limits;
int nElements = lastIndex - firstIndex + 1;

CCommandQueue::iterator ci = queue->begin() + lastIndex;
while (nElements > 0) {
--nElements;
const Command& qc = *ci;
if (qc.GetID() == CMD_WAIT) {
waitCommandsAI.RemoveWaitCommand(owner, qc);
}

if (facBuildQueue) {
// if ci == queue->begin() and !queue->empty(), this pop_front()'s
// via CFAI::ExecuteStop; otherwise only modifies *ci (not <queue>)
if (facCAI->RemoveBuildCommand(ci)) {
break;
}
}

if (!facCAI && (ci == queue->begin())) {
FinishCommand();
break;
}

queue->erase(ci);
// iterator may have been invalidated
ci = queue->begin() + firstIndex + nElements - 1;
}
repeatOrders = prevRepeat;
eventHandler.UnitCmdDone(owner, c);
return;
}

if (c.GetNumParams() <= 0)
return;

for (unsigned int p = 0; p < c.GetNumParams(); p++) {
const int removeValue = c.GetParam(p); // tag or id

Expand Down Expand Up @@ -1299,6 +1385,7 @@ void CCommandAI::ExecuteRemove(const Command& c)
}

repeatOrders = prevRepeat;
eventHandler.UnitCmdDone(owner, c);
}


Expand Down
2 changes: 2 additions & 0 deletions rts/Sim/Units/CommandAI/CommandAI.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ class CCommandAI : public CObject

void ExecuteInsert(const Command& c, bool fromSynced = true);
void ExecuteRemove(const Command& c);
const std::optional<std::pair<int, int>> GetRemoveLimitsFromOptions(const Command& c, const CCommandQueue& queue) const;
const std::optional<int> FindTagIndex(const CCommandQueue& queue, unsigned int tag) const;

void AddStockpileWeapon(CWeapon* weapon);
void StockpileChanged(CWeapon* weapon);
Expand Down