I am considering a use case where a complex UI rendering loop is linked to an Announcement, which is being fired rapidly (eg < 1ms), and overall system performance suffers. However the UI rendering loop really only needs to execute every 100ms since that is sufficient for a user to perceive an immediate response to their action. So I have rolled-my-own way to achieve this for which I was looking for some feedback...

1. Is there any existing feature in Pharo I have missed that can limit the firing rate of an announcement ? 2. Would this be useful to others as a feature to ship with Pharo ? (so I don't have to roll-my-own, and it gets more experienced eyes to ensure its right :) )
3. General comments on my approach, improvements, alternatives.

By way of a use case using Workspace, execute...
-------------
| announcer mainCount  uiCount |
announcer := Announcer new. "RateLimitingAnnouncer new."
mainCount := uiCount := 0.
Transcript crShow: 'ui' ; tab; show: 'main' .
announcer subscribe: AnnouncementMockA do:
[     uiCount := uiCount + 1.
   "Imagine a longer duration UI rendering loop here"
   Transcript crShow: uiCount asString , '       ' , mainCount asString.
].
[    10000 timesRepeat:
   [    mainCount := mainCount + 1.
       "(Delay forMilliseconds: 1 ) wait."
announcer announce: AnnouncementMockA new. ]
] timeToRun .
-------------
With 'Announcer' this takes 10 seconds to execute with the last lines being...
9999       9999
10000       10000

Replacing 'Announcer' with my own 'RateLimitingAnnouncer' listed below, this takes 20 milliseconds with the last lines being...
ui    main
1       1
2       10000

Uncommenting the "Delay" takes 30 seconds for Announcer - and 15 seconds for RateLimitingAnnouncer with last lines of...
289       9935
290       10000


Here is my code wrapping #announce: and #initialize...
-----
Announcer subclass: #RateLimitingAnnouncer
   instanceVariableNames: 'maxRateMilliSeconds queuedCounts'
   classVariableNames: ''
   poolDictionaries: ''
   category: 'LEKtrek-Core'
-----
RateLimitingAnnouncer >>initialize
   super initialize .
   maxRateMilliSeconds := 100.
   queuedCounts := Dictionary new.
-----
RateLimitingAnnouncer >>announce: anAnnouncement
   | announcementClass |

   "Track how many announcements fired since reset at end of forked Delay"
   announcementClass := anAnnouncement class.
   queuedCounts at: announcementClass
ifPresent: [ :count | queuedCounts at: announcementClass put: count + 1 ]
       ifAbsent: [ queuedCounts at: announcementClass put: 1 ].

"At first announcement since Delay'ed reset, forward this one and set up Delay to condense subsequent ones. " ( (queuedCounts at: announcementClass) = 1 ) ifTrue: [ [ "fire one announcement only for any announcement arriving within delay period." (Delay forMilliseconds: maxRateMilliSeconds) wait. [ (queuedCounts at: announcementClass) > 1 ] whileTrue: [ "At least one announcement arrived before end of Delay. Forward one announcement only and repeat" queuedCounts at: announcementClass put: 1.
               super announce: anAnnouncement.
(Delay forMilliseconds: maxRateMilliSeconds) wait. ].
           queuedCounts at: announcementClass put: 0.
] fork. ^ super announce: anAnnouncement. ].
-----

cheers -ben


Reply via email to