API for scheduling actions that do not run every tick

  • YP

    That should be easy to check for everyone with stats. Are ticks with % 10 === 0 slower then  others?

  • Culture

    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.

  • CoPS

    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.

  • 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.

  • Culture

    Hey now, that was my idea 😛

  • My apologies, Rob, I must have misread lol. Please note, I agree with Tedivm's idea of a `Game.seed` 😄

  • CoPS

    The seed idea is a much nicer approach to the issue. 


    +1 to tedivm's approach.

  • Culture

    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.

  • CoPS

    @db48x that introduces separate issues. 

    • Modifying Game.time makes simultaneous inter-player actions really hard to schedule.
    • Modifying % catches other situations - for example roomVisual effects that use any sort of repeating scale for visual differentiation.

    I agree with the principle, but your examples have many many flow on effects.

  • YP

    @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.

  • CoPS

    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.

  • YP

    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.

  • Culture

    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.