A technique to make sure an action doesn't occur more than once every N seconds

A few weeks ago I created a macro that keeps track of the last time you ran it, and will not run again if it ran in the last X seconds/minutes/hours.

Now I've achieved the same thing, but there's no macro required. It's all handled by inline statements. Here's how it looks when you use it:

The above code will play the "Tink" sound, but only once every 5 seconds at maximum, no matter how many times you run it? How? Let's open up the "Try" action a little, and we see this:

As you can see above, there are two green actions. One contains the number of seconds minimum times between, in this case, "Play Tink" actions. The other one contains assigns a unique name to the local variable "LocalZ" which I recommend starts with the letters "Timer" followed by any sequence of digits, but they should be unique. So I just press a bunch of random digits.

That's all there is to it. You just copy this action block anywhere in your app, and change the two green actions.

The real magic happens in the block called "Common Elements." You never need to open that block, but here's what it contains:

image

That code handles the logic of "don't let the code run if it has already run in the last N seconds." I won't explain it because there isn't any benefit to explaining it.

This code could be slightly simpler if there was a token that could return the UUID of the current action. I'm not sure if KM uses UUIDs to identify individual actions, so this may not be possible. But if it was possible, it would look like this:

I will upload the code in a few seconds after this paragraph...

Template Action for N second limited actions

Try-Catch.kmactions (5.3 KB)

5 Likes

This is a really interesting method! The Try/Catch is an action/logic/statement I seldom use in my own macros so I really appreciate how you utilise it here (and other macros you've shared utilising it). Making me better understand it and also inspires me wanting to use it more often myself.

A am very much a fan of alternate approaches to the same problem, and it’d be very interesting hearing your thoughts on where you're approach here differs from a more straightforward set up like this:

Keyboard Maestro Export

Group.kmactions (2.7 KB)

The biggest functional difference, the way I see it, might possibly be the possibility to add actions, always to be executed, also after the time limited sequence in the Catch?

Keyboard Maestro Export



I also want to take this opportunity to share a subroutine I’ve created in the past to have similar functionalities as the other approaches of this thread. But although it functions very well, it is more complex in it's execution, so the more simplistic approaches is probably more sensible in allot of situations.

The added complexity adds one major benefit though, and that is it not needing a unique global variable to be set for each macro utilising it; this one handles it all within a single global variable, differentiating the different callers by UUID. And this way, even though it's all handled within the same global variable, it still handles different macros, with possibly different lapse limits set, all possibly at the same time.

KM TriggerModifyers β€” TriggerLapseLimit Macros.kmmacros (15 KB)

Macro Notes

(KM v11.0.3)

TriggerLapseLimit subroutine, bundled with three test macros

  • The Test macros are set with hot key triggers: βŒƒβŒ₯⌘7, βŒƒβŒ₯⌘8 and βŒƒβŒ₯⌘9.
  • The TriggerLapseLimit subroutine sets a global variable named: "dnd__triggerLapseLimit"
Macro image β€” TriggerLapseLimit

Macro image β€” Test macros

Another benefit of this macro is that it supports two different modes: The ordinary "Since last pass” (or run or execution, or what one might call it) and "Since last triggered", where the latter only allows further execution if have not been actually triggered within the last set time limit.

  • Example: If set to a time limit of 2 seconds and re-triggered once every second it will not execute (past the subroutine) again after the first triggering, where as the ordinary "Since last pass" mode in this case would re-trigger once every 2 seconds.

I think I agree with you, in that your modification does prevent the addition of additional actions, as you are cancelling the macro (you should probably use "cancel just this macro" instead of "cancel this macro".) I don't see any other functional differences.

In any case, I think my solution, which does not cancel any macro, is a little easier to understand, because it's completely inline and doesn't cancel anything.

1 Like