Rate Limiting in GDScript

extends Node

func _ready():
    # First we'll look at basic rate limiting. Suppose
    # we want to limit our handling of incoming requests.
    # We'll serve these requests off an array of the same name.
    var requests = []
    for i in range(1, 6):
        requests.append(i)

    # This timer will fire every 200 milliseconds. This is the regulator
    # in our rate limiting scheme.
    var limiter = Timer.new()
    limiter.set_wait_time(0.2)
    limiter.set_one_shot(false)
    add_child(limiter)
    limiter.start()

    # By yielding to the timer before serving each request,
    # we limit ourselves to 1 request every 200 milliseconds.
    for req in requests:
        limiter.connect("timeout", self, "_on_limiter_timeout", [], CONNECT_ONESHOT)
        yield(limiter, "timeout")
        print("request ", req, " ", OS.get_datetime())

    # We may want to allow short bursts of requests in
    # our rate limiting scheme while preserving the
    # overall rate limit. We can accomplish this by
    # using a separate timer for bursty requests.
    var bursty_limiter = Timer.new()
    bursty_limiter.set_wait_time(0.2)
    bursty_limiter.set_one_shot(false)
    add_child(bursty_limiter)
    bursty_limiter.start()

    # Simulate 5 more incoming requests. The first 3 of these
    # will be processed immediately, benefiting from the burst capability.
    var bursty_requests = []
    for i in range(1, 6):
        bursty_requests.append(i)

    for req in bursty_requests:
        if bursty_requests.find(req) < 3:
            print("request ", req, " ", OS.get_datetime())
        else:
            bursty_limiter.connect("timeout", self, "_on_bursty_limiter_timeout", [], CONNECT_ONESHOT)
            yield(bursty_limiter, "timeout")
            print("request ", req, " ", OS.get_datetime())

func _on_limiter_timeout():
    pass

func _on_bursty_limiter_timeout():
    pass

This GDScript implementation of rate limiting uses Timers instead of channels, as GDScript doesn’t have built-in concurrency primitives like Go’s channels and goroutines.

Here’s how it works:

  1. We create an array of requests instead of using a channel.

  2. We use a Timer node to regulate the rate of processing. It fires every 200 milliseconds.

  3. We process each request by yielding to the timer, effectively waiting for 200 milliseconds between each request.

  4. For the bursty limiter, we use another Timer. The first three requests are processed immediately, simulating the burst capability. The remaining requests wait for the timer.

  5. We use OS.get_datetime() to get the current time for logging purposes.

  6. The _on_limiter_timeout and _on_bursty_limiter_timeout functions are empty placeholder functions that are called when the timers timeout. In this implementation, they’re not used for anything but are required for the connect function to work properly.

When you run this script, you should see the first batch of requests handled once every ~200 milliseconds as desired. For the second batch, the first 3 will be handled immediately, and the last 2 will be handled with ~200ms delays each.

Note that this is an approximation of Go’s behavior in GDScript. The exact timing may not be as precise as in Go due to differences in how GodotEngine handles timers and script execution.