API for scheduling actions that do not run every tick
This is not about optimizing individual CPU usage. At all. It's a suggestion, based off of tests run using the private server, for a way to potentially lower usage spikes (and thus reduce tick times). If you are not approaching this discussion from viewpoint then you're not understanding the discussion.
> This results in a situation where ticks that are a multiple of 5/10/whatever spike CPU usage for multiple players - a noticeable difference when only running multiple instances of myself on a private server, and presumably a serious difference on live.
*That* is what this suggestion is about.
I do agree that the proposed solution is overkill. I think a better solution would be some documentation and a single variable that is set each tick. I'm not sure how much of an effect this would actually have, but it's a worthwhile suggestion that involves minimal work for the developers for potentially some benefit.
> I would expect that most AIs have implemented proper execution control which offsets execution according to some frequency.
Maybe i've been spending too much time in #help and reviewing screeps code on github but plenty of players are not implementing offsets, they're doing a naive modulus check against Game.time.
I think the scheduling thing is something everybody could implement on its own .. I can think of thousands of things the devs could implement that is not possible for the player to create.
If you use the every 10 ticks example there are 9 possible offsets .. but even with random distribution over all players that does not mean that it will be random distributed with players on the current running node.
I try to distribute my workload even over all ticks .. by using `Game.time % 10 == 5` for some stuff for example or by using 9 or 11 instead of 10. Even worse if you use Game.time % 2 == 0 everywhere ,)
An other solution would be to create a official inofficial standard library with basic tools like that and some hints .. which is mentioned in the tutorial.
Atavus last edited by
That's why I opened with the assessment that I may be too "thick". I do get the point now. Most people stagger by just doing Game.time % frequency === 0. So labs are "synchronized" on the main world.
I hadn't thought of that.
Providing an alternative to Game.time such as Game.seed which is player specific might help mitigate this, however that requires that the player understands this additional property and uses it for the sole benefit of the "world health". Highly unlikely imho.
I'm not sure if there will be an easy method to mitigate this. However, before anything, it would make sense to have the devs actually do a code search to find how frequent these instances are and if they have a measurable impact on tick variability.
That should be easy to check for everyone with stats. Are ticks with % 10 === 0 slower then others?
Yes, every 20 ticks there is a slow tick. You can view this for yourself just by looking at the status website. I've attached a screenshot here for convenience.
Every 20 ticks there's a massive spike, and you can occasionally see smaller spikes on the other ticks divisible by ten. This is clearly an issue.
I'd like this feature with one caveat:
I'd like there to be an API that makes it apparent that it runs on average every X ticks. This should allow for more flexibility when scheduling. Generally I don't care about when these triggers happen, other than it happening too (in-) frequently.
SemperRabbit last edited by
I do like @Atavus's idea of a unique `Game.seed` per player. It could not only assist with staggering players' periodic code as suggested, it could also be used as a legit RNG seed to use for military purposes to foil ppl tailoring their military code to defeat visible flaws in yours. Random is not bad and would add another level to the military stratagem already in existence.
For the noobs, you could introduce it solely for the purpose of solving the current topic in the tutorials, and let them figure out other, ancillary uses later.
Hey now, that was my idea
SemperRabbit last edited by
My apologies, Rob, I must have misread lol. Please note, I agree with Tedivm's idea of a `Game.seed`
The seed idea is a much nicer approach to the issue.
+1 to tedivm's approach.
I agree with the Game.seed suggestion. It's a nice way to balance the tick times and avoid massive operations in 1 tick followed by nothing the next.
As a general philosophical principle, my preference is to slip any changes in at the lowest possible level so that the observable consequences are minimized.
Instead of introducing a new API function (which doesn't solve the problem unless people rewrite their code), or introducing a seed or player id value that can be added in (still requires people to rewrite their code), we should at least try to think of a solution that works even if people don't rewrite their code.
One obvious choice is to simply return a different value for game.ticks for each player. The server could simply have a unique offset for each player that it adds to the real tick number. That would immediately fix the problem. Of course it's not really unobservable; you'd get confused when you tried to match your logs with the replay, and you probably want to be able to coordinate attacks using the tick number as a clock.
So here's my real suggestion: change how mod works. Simply change the mod operator so that it adds the player's id number to the first operand before doing the mod operation. This doesn't change the domain or range of the operation, or change its behavior or usefulness, but it does solve the problem. On the down side, modifying v8 in this way could be annoying. You're forking a complicated component, which will have a maintenance cost down the road. Also, you'd have to dig into the JIT compiler so that it generates the correct machine code for the modified operation, which is not likely to be trivial. But from the philosophical point of view it would be ideal.
@db48x : how does changing the mod operator not change it usefulness? if you change it as suggest it is simply broken and results will be unexpected.
> how does changing the mod operator not change it usefulness? if you change it as suggest it is simply broken and results will be unexpected.
When you write Game.time % 10 == 0 (or any other constants), what's important is that this is true every 10th tick. If you change the behavior of the mod operator so that it adds a constant to the first operand (so that a%b becomes (a+c)%b), all you do is change which ticks this expression is true for. It doesn't change how _often_ it's true. This is better than changing the value of Game.time for each player (so that when your code sees Game.ticks == 42, mine would see Game.ticks = 53, even though it's really the same tick), because it doesn't hamper coordination between players, or between the player and the replay data.
@kotaru may have a valid objection that this would have unintended consequences, though I don't quite see what it would break for RoomVisuals. I guess if you wanted to use RoomVisuals to make a grid overlaying the room map? If you wanted a line across the map on every 10th row and column, to make a grid, then this could indeed be confusing. The x=0 row would not have a line when you did x%10==0, but it would still be doable. You would "simply" need to try the other constants 0 through 9 in the conditional to find out which one works. (Or figure out what your per-user constant is first, so that you could do the arithmetic to compensate). Or use a loop that increments by 10, like a normal person. I really think that the other downsides I mentioned are worse.
Because a colour visualizer will often use a transform e.g. `dist % 10` for comparing adjacent cells. Especially when you don't care about absolute value, and only relative difference, controlling colours like that is common (you see it very often in depth maps).
The major problem is that a user will unconditionally expect `a % b` to be the mathematical output, and that no behind-the-scenes transforms are applied to either, in any circumstance. This is a problem with any override.
People will use % for more cases than just Game.time, and the prior was just an example.
Oh yea, if you use mod to get repeating color bands in a distance field, it would come out off-center with that modification. That would be pretty surprising, and therefore bad.
Of course, when you're mucking around at that level you really can have two mod operators, one which only ever works on Game.time, and the other on anything else. You're sort of starting to add epicycles at that point, however. Or Game.time could return a value that is of a new type which subclasses Number, so that it can do all the usual numeric stuff, but with the trick mod operation. But then it's a boxed value, and that's not free either.
A fine problem!
Incidentally, I think the API you suggested is quite well-designed. I was going to suggest passing in a function like this:
Game.createRecurringTrigger(10, repeat=true, name="labTimer", runLabReactions);
But this has a big down-side, because the specification for when that function gets run becomes problematic (do they run before or after the rest of your code, what order do the triggers run in, etc). With the conditional you gave you can run the triggered code in whatever part of your code that you like, which is nice. On the other hand, I'd change the name "getTrigger" to something like "checkTrigger", because it really is a predicate. It doesn't return some object, it returns true or false.
It's not just about colors... It's about all stuff expecting a working modulus operator. There is a ingame encryption library for example to exchange data with your alliance over public segments. How long do you think somebody would need to debug his code until he finds out that someone messed with the operator. There is no place to document that sufficiently enough to not be a potential major pain in the ass.
Breaking the expected behaviors of common operators is a very dangerous move. I don't ever think it's a good plan.
As W4rl0ck points out, this would completely break the encryption module developed for public segments. I'm sure there are a variety of other things that we aren't considering which would also break. It would also require essentially forking node as far as I can tell.