Вопрос

I would like to write a Debounce procedure similar to ones commonly implemented in Javascript. For example the debounce function in Underscore.js.

I think it could look like this:

procedure Debounce(const TimeMS : integer; MyAnonymousProcedure : TProc);

and could be used like:

begin
  Debounce(200, procedure
  begin
    //Do something here...
  end);
end;

Possible Implementation #1

The Debounce() procedure would check how long it's been since the target method was called. The target method call would be delayed if it's been called in X time.

A pseudo code version:

procedure Debounce(const TimeMS : integer; MyAnonymousProcedure : TProc);
var
  TimeSinceCalled : integer;
begin
  TimeSinceCalled := FindTimeSinceProcedureLastCalled(MyAnonymousProcedure);
  if TimeMS < TimeSinceCalled then
  begin
    // The procedure hasn't been called recently,
    SaveTimeOfProcedureCall(MyAnonymousProcedure); // so save the current time...
    MyAnonymousProcedure;                          //  ...and call the procedure.
  end else
  begin
    // The procedure has been called recently so we use a timer
    // to call the procedure in X milliseconds.
    // The timer is reset each time Debounce() is called
    // so procedures will only be called when the Debounce time-out has
    // be passed.
    GlobalTimer.Reset;
    GlobalTimer.CallProcedureIn(MyAnonymousProcedure, TimeMS);
  end;
end;

The problem I'm facing is I need some way to identify the target procedure. My first thought was to use the method address of the target procedure as the ID. For example:
ID := Address(Foo); ID := Addr(Foo);
But this doesn't work with anonymous methods as they use the same address in my tests. Ideally the ID should be automatically generated. Any ideas?

Это было полезно?

Решение

You say the addresses of all your anonymous procedures are the same, but that's impossible. Instead, I suspect you're trying the wrong value. You say you call Address, but there's no such function. You probably mean Addr, which is the same as the @ operator. However, that gives you the address of the MyAnonymousProcedure formal parameter, not the address stored in it. It's quite possible that the parameter has the same address each time you call the function.

You're looking for a way of identifying distinct procedure references. The reference itself should suffice. You could use a TDictionary<TProc, TDateTime> to map procedures to their last call times.

The danger is that each time you run you call your Debounce function with a procedure literal, you may end up constructing an entirely new instance of that procedure object, so its address will be different from any previously constructed procedure, even if it uses the same code and the same captured variable values.

The JavaScript version is susceptible to the same problem, which is why it doesn't try to identify the function at all. Instead, it returns a new function that wraps the one passed in. The new function adds the delay behavior. In Delphi, it could work something like this:

function Debounce(Delay: TDateTime; Proc: TProc): TProc;
var
  LastCall: TDateTime;
begin
  LastCall := 0;
  Result := procedure
  begin
    if LastCall + Delay <= Now then
      Proc;
    LastCall := Now;
  end;
end;

This version works as though the immediate parameter in the Underscore version is true. That's the easy case because it doesn't require any mechanism for delaying execution. However, it shows that you don't really need a way of distinguishing between different procedure references, and that's what you were stuck on before.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top