Skip to content

Commit 3e22c86

Browse files
committed
chore: debounce build all tokens call
1 parent a86f479 commit 3e22c86

File tree

6 files changed

+172
-22
lines changed

6 files changed

+172
-22
lines changed

src/app_service/service/general/debouncer.nim

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import nimqml, os
22

3+
import universal_container
34
import app/core/tasks/[qt, threadpool]
45

56

@@ -8,22 +9,40 @@ include app_service/common/async_tasks
89
QtObject:
910
type Debouncer* = ref object of QObject
1011
threadpool: ThreadPool
11-
callback: proc()
12+
callback0: proc()
13+
callback1Wrapper: proc()
14+
callback2Wrapper: proc()
1215
delay: int
1316
checkInterval: int
1417
remaining: int
1518

19+
params: Container
20+
1621

1722
proc delete*(self: Debouncer)
18-
proc newDebouncer*(threadpool: ThreadPool, delayMs: int, checkIntervalMs: int, callback: proc()): Debouncer =
23+
proc newDebouncer*(threadpool: ThreadPool, delayMs: int, checkIntervalMs: int): Debouncer =
1924
new(result, delete)
2025
result.QObject.setup
2126
if checkIntervalMs > delayMs:
2227
raise newException(ValueError, "checkIntervalMs must be less than delayMs")
2328
result.threadpool = threadpool
24-
result.callback = callback
2529
result.delay = delayMs
2630
result.checkInterval = checkIntervalMs
31+
result.params = newContainer()
32+
33+
proc registerCall0*(self: Debouncer, callback: proc()) =
34+
self.callback0 = callback
35+
36+
proc registerCall1*[T1](self: Debouncer, callback: proc(p1: T1)) =
37+
self.callback1Wrapper = proc() =
38+
let param0 = getValueAtPosition[T1](self.params, 0)
39+
callback(param0)
40+
41+
proc registerCall2*[T1, T2](self: Debouncer, callback: proc(p1: T1, p2: T2)) =
42+
self.callback2Wrapper = proc() =
43+
let param0 = getValueAtPosition[T1](self.params, 0)
44+
let param1 = getValueAtPosition[T2](self.params, 1)
45+
callback(param0, param1)
2746

2847
proc runTimer*(self: Debouncer) =
2948
let arg = TimerTaskArg(
@@ -37,15 +56,57 @@ QtObject:
3756
proc onTimeout(self: Debouncer, response: string) {.slot.} =
3857
self.remaining = self.remaining - self.checkInterval
3958
if self.remaining <= 0:
40-
self.callback()
59+
if not self.callback0.isNil:
60+
self.callback0()
61+
elif not self.callback1Wrapper.isNil:
62+
self.callback1Wrapper()
63+
elif not self.callback2Wrapper.isNil:
64+
self.callback2Wrapper()
4165
else:
4266
self.runTimer()
4367

4468
proc call*(self: Debouncer) =
4569
let busy = self.remaining > 0
4670
if busy:
4771
return
72+
self.params.clear()
73+
self.remaining = self.delay
74+
self.runTimer()
4875

76+
proc call*[T1](self: Debouncer, param0: T1) =
77+
let busy = self.remaining > 0
78+
if busy:
79+
return
80+
self.params.clear()
81+
self.params.add(param0)
82+
self.remaining = self.delay
83+
self.runTimer()
84+
85+
proc call*[T1, T2](self: Debouncer, param0: T1, param1: T2) =
86+
let busy = self.remaining > 0
87+
if busy:
88+
## params check, while the call is waiting to be called
89+
## TODO: if needed we can add the queue of pending params to be called after the current call is completed
90+
##
91+
## FOR NOW: since the only usage is for buildAllTokens, we add ONLY those accounts (which is the first param (on position 0))
92+
## to list of accounts if they are not in the list yet.
93+
var currentAccounts = getValueAtPosition[T1](self.params, 0) # refers to accounts of the `buildAllTokens` call
94+
let forceRefresh = getValueAtPosition[T2](self.params, 1) # refers to forceRefresh of the `buildAllTokens` call
95+
96+
var update = false
97+
for account in param0:
98+
if not currentAccounts.contains(account):
99+
update = true
100+
currentAccounts.add(account)
101+
102+
if update:
103+
self.params.clear()
104+
self.params.add(currentAccounts)
105+
self.params.add(forceRefresh)
106+
return
107+
self.params.clear()
108+
self.params.add(param0)
109+
self.params.add(param1)
49110
self.remaining = self.delay
50111
self.runTimer()
51112

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
2+
type
3+
ItemKind* = enum
4+
vkInt, vkString, vkFloat, vkBool, vkSeqInt, vkSeqString
5+
6+
Item* = object
7+
case kind: ItemKind
8+
of vkInt: intVal: int
9+
of vkString: strVal: string
10+
of vkFloat: floatVal: float
11+
of vkBool: boolVal: bool
12+
of vkSeqInt: seqIntVal: seq[int]
13+
of vkSeqString: seqStringVal: seq[string]
14+
15+
Container* = ref object
16+
items: seq[Item]
17+
18+
proc newContainer*(): Container =
19+
return new(Container)
20+
21+
proc clear*(self: Container) =
22+
self.items = @[]
23+
24+
proc add*(self: Container, item: int) =
25+
self.items.add(Item(kind: vkInt, intVal: item))
26+
27+
proc add*(self: Container, item: string) =
28+
self.items.add(Item(kind: vkString, strVal: item))
29+
30+
proc add*(self: Container, item: float) =
31+
self.items.add(Item(kind: vkFloat, floatVal: item))
32+
33+
proc add*(self: Container, item: bool) =
34+
self.items.add(Item(kind: vkBool, boolVal: item))
35+
36+
proc add*(self: Container, item: seq[int]) =
37+
self.items.add(Item(kind: vkSeqInt, seqIntVal: item))
38+
39+
proc add*(self: Container, item: seq[string]) =
40+
self.items.add(Item(kind: vkSeqString, seqStringVal: item))
41+
42+
proc getInt(v: Item): int =
43+
assert v.kind == vkInt, "Expected int but got " & $v.kind
44+
return v.intVal
45+
46+
proc getStr(v: Item): string =
47+
assert v.kind == vkString, "Expected string but got " & $v.kind
48+
return v.strVal
49+
50+
proc getFloat(v: Item): float =
51+
assert v.kind == vkFloat, "Expected float but got " & $v.kind
52+
return v.floatVal
53+
54+
proc getBool(v: Item): bool =
55+
assert v.kind == vkBool, "Expected bool but got " & $v.kind
56+
return v.boolVal
57+
58+
proc getSeqInt(v: Item): seq[int] =
59+
assert v.kind == vkSeqInt, "Expected seq[int] but got " & $v.kind
60+
return v.seqIntVal
61+
62+
proc getSeqString(v: Item): seq[string] =
63+
assert v.kind == vkSeqString, "Expected seq[string] but got " & $v.kind
64+
return v.seqStringVal
65+
66+
proc extract*[T](v: Item): T =
67+
when T is int:
68+
return v.getInt()
69+
elif T is string:
70+
return v.getStr()
71+
elif T is float:
72+
return v.getFloat()
73+
elif T is bool:
74+
return v.getBool()
75+
elif T is seq[int]:
76+
return v.getSeqInt()
77+
elif T is seq[string]:
78+
return v.getSeqString()
79+
else:
80+
raise "Unsupported type for extraction"
81+
82+
proc getItemAtPosition*(self: Container, position: int): Item =
83+
assert position >= 0 and position < self.items.len
84+
return self.items[position]
85+
86+
proc getValueAtPosition*[T](self: Container, position: int): T =
87+
let item = self.getItemAtPosition(position)
88+
return extract[T](item)

src/app_service/service/token/service_main.nim

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ proc init*(self: Service) =
3737
# this is the delay before the first call to the callback, this is an action that doesn't need to be called immediately, but it's pretty expensive in terms of time/performances
3838
# for example `wallet-tick-reload` event is emitted for every single chain-account pair, and at the app start can be more such signals received from the statusgo side if the balance have changed.
3939
# Means it the app contains more accounts the likelihood of having more `wallet-tick-reload` signals is higher, so we need to delay the rebuildMarketData call to avoid unnecessary calls.
40-
delayMs = 3000,
41-
checkIntervalMs = 500,
42-
callback = proc() = self.rebuildMarketDataInternal())
40+
delayMs = 1500,
41+
checkIntervalMs = 500)
42+
self.rebuildMarketDataDebouncer.registerCall0(callback = proc() = self.rebuildMarketDataInternal())
4343

4444
self.events.on(SignalType.Wallet.event) do(e:Args):
4545
var data = WalletSignal(e)

src/app_service/service/wallet_account/service.nim

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import web3/eth_api_types
44

55
import app/global/global_singleton
66

7+
import app_service/service/general/debouncer as debouncer_service
78
import app_service/service/settings/service as settings_service
89
import app_service/service/accounts/service as accounts_service
910
import app_service/service/token/service as token_service
@@ -48,11 +49,11 @@ QtObject:
4849
keypairs: Table[string, KeypairDto] ## [keyUid, KeypairDto]
4950
groupedAssets: seq[AssetGroupItem]
5051
hasBalanceCache: bool
51-
fetchingBalancesInProgress: bool
52-
addressesWaitingForBalanceToFetch: seq[string]
52+
buildTokensDebouncer: debouncer_service.Debouncer
5353

5454
# Forward declaration
5555
proc buildAllTokens*(self: Service, accounts: seq[string], forceRefresh: bool)
56+
proc buildAllTokensInternal(self: Service, accounts: seq[string], forceRefresh: bool)
5657
proc handleWalletAccount(self: Service, account: WalletAccountDto, notify: bool = true)
5758
proc handleKeypair(self: Service, keypair: KeypairDto)
5859
proc updateAccountsPositions(self: Service)

src/app_service/service/wallet_account/service_account.nim

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ proc startWallet(self: Service) =
138138

139139
proc init*(self: Service) =
140140
try:
141+
self.buildTokensDebouncer = debouncer_service.newDebouncer(
142+
self.threadpool,
143+
# this is the delay before the first call to the callback, this is an action that doesn't need to be called immediately, but it's pretty expensive in terms of time/performances
144+
# for example `wallet-tick-reload` event is emitted for every single chain-account pair, and at the app start can be more such signals received from the statusgo side if the balance have changed.
145+
# Means it the app contains more accounts the likelihood of having more `wallet-tick-reload` signals is higher, so we need to delay the rebuildMarketData call to avoid unnecessary calls.
146+
delayMs = 3000,
147+
checkIntervalMs = 1000)
148+
self.buildTokensDebouncer.registerCall2(callback = proc(accounts: seq[string], forceRefresh: bool) = self.buildAllTokensInternal(accounts, forceRefresh))
149+
141150
var addressesToGetENSName: seq[string] = @[]
142151
let chainId = self.networkService.getAppNetwork().chainId
143152
let woAccounts = getWatchOnlyAccountsFromDb()

src/app_service/service/wallet_account/service_token.nim

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,13 @@ proc onAllTokensBuilt(self: Service, response: string) {.slot.} =
55
var accountAddresses: seq[string] = @[]
66
var groupedAssets: seq[AssetGroupItem] = @[]
77
defer:
8-
self.fetchingBalancesInProgress = false
98
let timestamp = getTime().toUnix()
109
self.events.emit(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT, TokensPerAccountArgs(
1110
accountAddresses:accountAddresses,
1211
assets: groupedAssets,
1312
timestamp: timestamp
1413
))
1514

16-
if self.addressesWaitingForBalanceToFetch.len > 0:
17-
let addressesToFetch = self.addressesWaitingForBalanceToFetch
18-
self.addressesWaitingForBalanceToFetch = @[]
19-
self.buildAllTokens(addressesToFetch, forceRefresh = true)
20-
2115
try:
2216
let responseObj = response.parseJson
2317
var resultObj: JsonNode
@@ -91,17 +85,11 @@ proc onAllTokensBuilt(self: Service, response: string) {.slot.} =
9185
except Exception as e:
9286
error "error: ", procName="onAllTokensBuilt", errName = e.name, errDesription = e.msg
9387

94-
proc buildAllTokens*(self: Service, accounts: seq[string], forceRefresh: bool) =
88+
proc buildAllTokensInternal(self: Service, accounts: seq[string], forceRefresh: bool) =
9589
if not main_constants.WALLET_ENABLED or
9690
accounts.len == 0:
9791
return
9892

99-
if self.fetchingBalancesInProgress:
100-
self.addressesWaitingForBalanceToFetch.add(accounts)
101-
return
102-
103-
self.fetchingBalancesInProgress = true
104-
10593
# set assetsLoading to true as the tokens are being loaded
10694
for waddress in accounts:
10795
self.updateAssetsLoadingState(waddress, true)
@@ -116,6 +104,9 @@ proc buildAllTokens*(self: Service, accounts: seq[string], forceRefresh: bool) =
116104
)
117105
self.threadpool.start(arg)
118106

107+
proc buildAllTokens*(self: Service, accounts: seq[string], forceRefresh: bool) =
108+
self.buildTokensDebouncer.call(accounts, forceRefresh)
109+
119110
# Returns the total currency balance for the given wallet accounts and chain ids
120111
proc getTotalCurrencyBalance*(self: Service, walletAccounts: seq[string], chainIds: seq[int]): float64 =
121112
var totalBalance: float64 = 0.0

0 commit comments

Comments
 (0)