Skip to content

Add gadget:TeamResourceExcess(teamID, metal, energy) -> bool#2530

Closed
sprunk wants to merge 1 commit into
masterfrom
team-resource-excess-callin
Closed

Add gadget:TeamResourceExcess(teamID, metal, energy) -> bool#2530
sprunk wants to merge 1 commit into
masterfrom
team-resource-excess-callin

Conversation

@sprunk

@sprunk sprunk commented Aug 15, 2025

Copy link
Copy Markdown
Collaborator

The resources are the amount over the storage max. Return true to take over engine handling; otherwise the native behaviour of buffering it until the next slowupdate will take place.

@badosu

badosu commented Sep 18, 2025

Copy link
Copy Markdown
Collaborator

I wonder, since the callin invocation instances scale with the number of teams per gameframe (the ones overflowing that is), whether it would make sense to have the callin grouped? table<teamId, { [string resourceName] = [number resourceAmount] }>.

this signature also future proofs engine supported custom resourcing (although single per team can be made that way too).

@sprunk

sprunk commented Sep 18, 2025

Copy link
Copy Markdown
Collaborator Author

this signature also future proofs engine supported custom resourcing (although single per team can be made that way too).

The metal, energy were just for clarity, the actual signature is gadget:TeamResourceExcess(teamID, res1, res2, res3, ..., resN) -> bool so custom resources are sorted:

lua_pushnumber(L, teamID);
for (int i = 0; i < excess.MAX_RESOURCES; ++i)
lua_pushnumber(L, excess[i]);

If somebody wants a table they can declare the function as gadget:TeamResourceExcess(teamID, ...) since then resources will end up packed into the ... variadic table.

@sprunk

sprunk commented Sep 18, 2025

Copy link
Copy Markdown
Collaborator Author

I wonder, since the callin invocation instances scale with the number of teams per gameframe

Right now it's not per gameframe. It happens for every instance of adding resources that puts you above the max. So for example this will produce 4 events in one frame:

 -- assume teamID 0 has 200 current, 1000 max metal
+Spring.AddTeamResource(0, "metal", 500) -- no event, we're at 700/1000
+Spring.AddTeamResource(0, "metal", 500) -- event with 200 excess, we're at 1000/1000
+Spring.AddTeamResource(0, "metal", 500) -- event with 500 excess, we're at 1000/1000
-Spring.UseTeamResource(0, "metal", 400) -- we're at 600/1000
+Spring.AddTeamResource(0, "metal", 300) -- no event, we're at 900/1000
+Spring.AddTeamResource(0, "metal", 500) -- event with 400 excess, we're at 1000/1000

and this will produce 0 events (neither immediately nor at the end of frame):

 -- assume teamID 0 has 200 current, 1000 max metal
+Spring.AddTeamResource(0, "metal", 100) -- no event, we're at 300/1000
+Spring.AddTeamResource(0, "metal", 100) -- no event, we're at 400/1000
+Spring.AddTeamResource(0, "metal", 100) -- no event, we're at 500/1000

This is more powerful than aggregating income from the whole frame, since you get information about individual instances and their order (which can matter if you intertwine multiple Add and Use within one frame) but can still handle it by just adding to an accumulator handled as a whole at the end of frame in gadget:GameFramePost.

That said, right now if there's 10k windgens in a game then each will produce an instance of +5e or whatever and each will have an accompanying event every slowupdate. So maybe there's a performance argument to aggregating it, though maybe the better thing to do would be aggregating native income from units into one instance (would also be good for ECS afaict).

@badosu

badosu commented Sep 18, 2025

Copy link
Copy Markdown
Collaborator

Right now it's not per gameframe. It happens for every instance of adding resources that puts you above the max. So for example this will produce 4 events in one frame:
...
That said, right now if there's 10k windgens in a game then each will produce an instance of +5e or whatever and each will have an accompanying event every slowupdate. So maybe there's a performance argument ...

I see. I didn't look in-depth into this but this implications does not seem acceptable (to me), or maybe I'm overestimating the overhead impact?

This is more powerful than aggregating income from the whole frame, since you get information about individual instances and their order (which can matter if you intertwine multiple Add and Use within one frame)...

One question: how is it powerful? Which use cases does it enable? Shouldn't the resource delta source, or some other related data, be required to enable any use case where separate instances in a gameframe must be used for some feature?

but can still handle it by just adding to an accumulator handled as a whole at the end of frame in gadget:GameFramePost.

A proposal

Maybe this proposal might be "hacky" in its most naive implementation, but I'd seriously consider just accumulating the excess (properly accounting for negative deltas in gameframe) from pre- to post- gameframe sym, and then asking lua how to handle the excess.

A few cons:
  1. Games don't have granularity to control the synchronous ordered execution of events, related to resources that would otherwise be available within the gameframe. E.g.: "1. team1 overflowed resource 2. team2 can't invest the resource in the buildprogress tick they would otherwise 3. excess resource is available at the start of next gameframe only".
  2. Depending on implementation, can either be: breaking or complex/hacky.
Remarks:
  • On breaking: It would be, strictly speaking, breaking in regard to the simulation behavior being different from previous without fallback. However:
    1. this does not mean any game would be "broken", the gameplay impact should be practically non existent, but I accept I could be underestimating.
    2. Relying on internal engine implementation details for calculating the frame does not sound solid. This would be as "breaking" as a refactor on gameframe calculation could be (assuming coherent callin lifecycle preservation).
    3. (a minor diversion) An effort disentangling synchronous gameframe sym execution (say, some attempt at concurrency for the more hairy subsystems gameframe calculation) could imply, naturally, some form of post-sym callin dispatch (the resulting synced state changes being carried over to the next gameframe). This is in line with that. But this is just a diversion.
  • On complexity: Avoiding breaking we require complex and possibly hacky solutions, e.g.: switch to old system on some constraint, game has to set some engine internal tied option - both of which do not seem too viable for me.

Keeping in mind, both considerations only exist in the scenario we allow the callin to alter normal overflow execution (instead of a purely "notification" callin), which I think is our intent.

The resources are the amount over the storage max.
Return true to take over engine handling; otherwise
the native behaviour of buffering it until the next
slowupdate will take place.
@sprunk sprunk force-pushed the team-resource-excess-callin branch 2 times, most recently from 9bf24c8 to d734d53 Compare September 24, 2025 02:27
@sprunk

sprunk commented Sep 24, 2025

Copy link
Copy Markdown
Collaborator Author

@badosu aggregating within a gameframe sounds good, I pushed something.

Comment thread rts/Sim/Misc/TeamHandler.cpp
@sprunk sprunk marked this pull request as ready for review September 24, 2025 16:17
@sprunk

sprunk commented Sep 24, 2025

Copy link
Copy Markdown
Collaborator Author

Status:

  • ✅ design seems done (feedback welcome though)
  • ❌ was written blind (just compiles) so yet needs to actually be tested. I'm somewhat busy so anybody is free to pick it up, though I might be able to do it on the weekend.

@badosu

badosu commented Sep 24, 2025

Copy link
Copy Markdown
Collaborator
* ❌ was written blind (just compiles) so yet needs to actually be tested. I'm somewhat busy so anybody is free to pick it up, though I might be able to do it on the weekend.

Sounds good, I'll see if I can manage a build and test locally.

@sprunk

sprunk commented Oct 22, 2025

Copy link
Copy Markdown
Collaborator Author

Missing the basecontent gadgethandler part

@badosu

badosu commented Oct 22, 2025

Copy link
Copy Markdown
Collaborator

@sprunk One perhaps non-intuitive implication:

res.metal += resDelayedShare.metal; resDelayedShare.metal = 0.0f;
res.energy += resDelayedShare.energy; resDelayedShare.energy = 0.0f;

Resource adding increments resOverflow, but resOverflow only increments resDelayedShare on that gameframe section. I would have to be more certain, but it seems to me there could be a case of lost overflow by 1 frame (need to check slowupdate happens before or after gameframe team).

@sprunk

sprunk commented Oct 22, 2025

Copy link
Copy Markdown
Collaborator Author

Slowupdate (line 132) happens after GameFramePost (line 122)

teams[a].GameFramePost(frameNum);
}
if ((frameNum % TEAM_SLOWUPDATE_RATE) != 0)
return;
for (int a = 0; a < ActiveTeams(); ++a) {
teams[a].ResetResourceState();
}
for (int a = 0; a < ActiveTeams(); ++a) {
teams[a].SlowUpdate();


/*** Called when excess resources are added.
* Accumulates all excesses within a single gameframe.
*

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Should document what happens if you generate more excess while inside this event.

@sprunk

sprunk commented Nov 6, 2025

Copy link
Copy Markdown
Collaborator Author

I'm warming up to the idea of having a single call with a table containing all the teams, since there are practical differences compared to e.g. targeting.

@sprunk

sprunk commented Nov 14, 2025

Copy link
Copy Markdown
Collaborator Author

I wonder, since the callin invocation instances scale with the number of teams per gameframe (the ones overflowing that is), whether it would make sense to have the callin grouped? table<teamId, { [string resourceName] = [number resourceAmount] }>.

I opened #2642 which implements this and which I think I like more now after seeing how gamedevs actually use them.

@sprunk sprunk marked this pull request as draft November 14, 2025 07:20
@sprunk

sprunk commented Nov 26, 2025

Copy link
Copy Markdown
Collaborator Author

Superseded by #2642

@sprunk sprunk closed this Nov 26, 2025
@sprunk sprunk mentioned this pull request Nov 27, 2025
@sprunk sprunk deleted the team-resource-excess-callin branch December 29, 2025 19:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants