-
Notifications
You must be signed in to change notification settings - Fork 6
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
fix: Fix gas saturation, #34 #60
base: master
Are you sure you want to change the base?
Conversation
abb2744
to
9af04ce
Compare
May want to accept this before #61, as there's prolly conversation about what scout-worker attack should do. |
src/objects/GameObject.cpp
Outdated
} | ||
|
||
GameObject::GameObject(const sc2::Unit& unit_): m_tag(unit_.tag) { | ||
GameObject::GameObject(const sc2::Unit& unit_): m_unit(&unit_), m_tag(unit_.tag) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are going to keep sc2::Unit inside please pass it by pointer. Please, check the linter in advance.
src/objects/GameObject.cpp
Outdated
@@ -6,9 +6,10 @@ | |||
#include "core/API.h" | |||
|
|||
GameObject::GameObject(sc2::Tag tag_): m_tag(tag_) { | |||
m_unit = gAPI->observer().GetUnit(tag_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need this constructor now? Also, with this approach ToUnit
should be removed.
src/objects/GameObject.h
Outdated
@@ -24,6 +24,8 @@ struct GameObject { | |||
|
|||
static sc2::Unit ToUnit(sc2::Tag tag_); | |||
|
|||
const sc2::Unit* m_unit; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: Just const sc2::Unit* unit
as it is not a member.
src/plugins/Miner.cpp
Outdated
@@ -44,7 +44,12 @@ void SecureVespeneIncome() { | |||
auto refineries = gAPI->observer().GetUnits(IsRefinery()); | |||
|
|||
for (const auto& i : refineries()) { | |||
if (i->assigned_harvesters >= i->ideal_harvesters) | |||
if (i->assigned_harvesters > i->ideal_harvesters) { | |||
gHub->RemoveVespeneHarvester(*i); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I think the problem is in the assignment logic. How it happens that we have extra harvesters?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SCV that builds the Refinery takes 5+ frames before it exits the 'geyser' construction area and turns around and joins the refinery. So for Terran even with perfect-working assignment, there will be 4/3. Unless we keep a separate list of vespene-workers-per-refinery.
Other than that, I think it takes ~5 frames from when the building is finished to when the "assigned_harvesters" shows a valid value. It's possible it doesn't check every frame to update "assigned_harvesters" as I have seen up to 8/3 for a geyser, which makes me think it checks every 5 frames, so if assigning the 3rd vespener on frame 1000, it will still show 2/3 (1 frame action-lag) until frame 1005, when it updates to 8/3 (including the builder).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, the first mitigation: we can manually assign that building scv as harvester.
The second could be making miner trigger once in 6 or more frames. So this lag could be skipped.
The third, I think is to ignore the data, provided by the game and get list of the assigned harvesters from the cache somehow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just running SecureVespene only every 10 frames does fix this problem completely. Fix is very simple now. :)
9af04ce
to
3bbc684
Compare
src/plugins/Miner.cpp
Outdated
@@ -140,7 +140,8 @@ void DistrubuteMineralWorker(const sc2::Unit* unit_) { | |||
|
|||
void Miner::OnStep(Builder* builder_) { | |||
SecureMineralsIncome(builder_); | |||
SecureVespeneIncome(); | |||
if (gAPI->observer().GetGameLoop() % 10 == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps the whole plugin should be launched e.g. every 10th frame?
Another interesting question: how does it work with increased frames per turn value and in realtime?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good points. I've refactored this to be part of Plugin (and changed QuarterMaster's frames_to_skip logic to match).
3bbc684
to
1d4fc36
Compare
Give this a look -- it's the best I can do without making a Plugin.cpp or making functions with bodies in Plugin.h |
There is nothing bad in adding Plugin.cpp. Plugin should stay pure virtual, the rest is not so valuable. |
1d4fc36
to
5052fdd
Compare
Here's a stab at what might work. I added worker-training in Miner::OnIdle since it won't check every frame anymore (Miner::OnStep called every 7 frames), and losing 7 frames (at most) after each worker training is a little too long. |
b4f7403
to
487d28e
Compare
src/plugins/Plugin.h
Outdated
virtual void UpdateNextRunTurn(); | ||
|
||
protected: | ||
uint32_t m_run_every_x_turns; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m_run_every_x_turns
-> m_frequency
.
And I assume it should be const and private. Later it is enough to check the current step by calling the module operator as you did before.
As for m_next_run_turn
, I believe it is not needed. Working with frequency should be enough, cause it serves the same purpose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You said earlier "Another interesting question: how does it work with increased frames per turn value and in realtime?" -- and that is why I had to add m_next_run_turn, because modulo will not work with "increased frames per turn value" or in RealTime, because GetGameLoop is in total frames, and not number of times that Dispatcher::OnStep has been called.
I feel that 'turn' was/is ambiguous in that I thought 'turns' meant 'frames' (GameLoops / Game Turns) but now it seems it means 'steps' (based on SetStepSize/"frames per turn value"). I think the bot variables should only think in 'frames', because otherwise changing to RealTime or changing StepSize would require many small changes to any timing values. I started using 'turns' because you were using that, but I think 'frames' should be used for precise clarity. Because of this "m_frequency" can be confusing as it changes meaning if you set RealTime/StepSize>=1, so using "m_frame_frequency" avoids this ambiguity. I think we should avoid using the word 'turn' as it is ambiguous on whether it means 'frame' or 'step'.
Actually, I guess since the API uses "gameloops" itself, it would be most consistent to use gameloops also to refer to them. "m_gameloop_frequency" and "m_next_run_gameloop".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, if you want to check frames, not count of steps, you should:
a. Keep the actual frames count (or even seconds) in dispatcher.
b. Use modulo on the frames (seconds) count, i.e. call a method from plugin with the current count of passed frames as argument and expect that it returns true or false.
I believe there is no need in storing anything except frequency inside the plugins.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The term frequency means how often a plugin should be called, the actual value against it is tested could be different, but the term itself, imho, very descriptive. There is no way to identify how yours or mine naming approaches work without reading the code, thus I prefer shorter namings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, if you want to check frames, not count of steps, you should:
a. Keep the actual frames count (or even seconds) in dispatcher.
b. Use modulo on the frames (seconds) count, i.e. call a method from plugin with the current count of passed frames as argument and expect that it returns true or false.
I believe there is no need in storing anything except frequency inside the plugins.
b. Modulo does not work for GameLoops(frames) with StepSize >= 1. It will 'miss' frequencies on non-exact GameLoops, leading to longer waits than intended.
StepSize=2 , plugin->m_frequency=3 (Dispatcher D)
GameLoop:0, Step:0, D::OnStep called , 0 % 3 == 0 , plugin->OnStep CALLED
GameLoop:1, Step:-, D::OnStep skipped, 1 % 3 == 1 , plugin->OnStep not checked
GameLoop:2, Step:1, D::OnStep, 2 % 3 == 2 , plugin->OnStep skipped
GameLoop:3, Step:-, skipped, 3 % 3 == 0 , plugin->OnStep not checked
<- MISSED FREQUENCY
GameLoop:4, Step:2, D::OnStep, 4 % 3 == 1 , plugin->OnStep skipped
GameLoop:5, Step:-, skipped, 5 % 3 == 2 , plugin->OnStep not checked
GameLoop:6, Step:3 D:::OnStep, 6 % 3 == 0 , plugin->OnStep CALLED (waited 6 GameLoops)
Modulo only works if it is checked on the 'exact' gameloop/frame, therefore missing frequencies on the 'skipped' Dispatcher::OnStep's due to StepSize>=1.
If we use 'Step Frequency' instead of 'GameLoop Frequency', then the tuned timings will be off if the StepSize is ever changed, but maybe that is okay and authors can update the plugins' m_frequencies to match. I will push a commit that does it the 'Step Frequency' way (using m_steps in Dispatcher as you suggested in (a)). Let me know if that is more towards what you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets reshape this logic a bit:
-
Please, move all the checking parts into Dispatcher as this idea is closer to it (e.g. consider that we issue onStep event or block it if needed).
-
If modulo doesn't work well, we can do a bit different approach similar to algorithms used in computer networks (transfer of packets on the network level). Some (or perhaps many) network cards drivers act in similar manner: they count 'time left' for each sent packet and if no response received, packet loss is assumed.
So, instead of determining when a plugin should be called, we can track exactly 'how many turns left for a plugin to be called'.
The scheme is following:
a. Dispatcher has an actual map of 'turns left - a plugin'.
b. Each plugin reports how much time left to call it again on first call of onStep method.
c. Dispatcher fills in the map.
d. Dispatcher reduces each counter in the map each onStep by operating current frames count per step value.
e. If counter becomes <= 0, we call onStep of corresponding plugin, take return values from it and start the countdown again.
Such approach provides the following benefits:
- Very precise timer. We use exact number of passed frames.
- A plugin can report different frequencies (seems that this the case with Miner), even sleep for e.g. 300 frames at the beginning of a game.
- Technically a plugin can ask Dispatcher to increase counter by some reason or reset it, but so far we don't need such functionality.
- All control flow is located in single place - Dispatcher.
Please, let me know what do you think about this idea and sorry for so long review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better, but requires changes.
487d28e
to
18a41b5
Compare
Only check vespene workers every 7 steps, to prevent too many vespene workers from being added and oversaturated. Also, create functionality for other Plugin-derived classes to run every X steps.
18a41b5
to
fc726bc
Compare
75b66e5
to
13305b3
Compare
If gas oversaturated, remove from busy_workers and call DistributeWorker.
If gas under-saturated and any base is mineral-oversaturated, send one of those mineral workers to that gas.
I think this can be accepted separately from Repair and AttackScout?