The `TaskManager` threadpool is a memory-safe replacement for the `spawnAndSend` operations that are currently causing memory issues in status-desktop.
From a fundamental memory management point of view, `libstatus/settings`, `libstatus/contracts`, and `libstatus/tokens` (custom tokens) have all been converted to `{.threadvar.}`s and `Atomic[bool]`s to maintain the cache and `dirty` flag across threads, respectively, eliminating the need for thread locks and incorrect `{.gcsafe.}` compiler overrides.
The successful [recyclable threadpool experiment from `nim-task-runner`](https://github.com/status-im/nim-task-runner/blob/test/use-cases/test/use_cases/test_sync.nim) using `AsyncChannel[ThreadSafeString]`s was brought over to `status-desktop` and implemented in somewhat of a hardcoded manner, as we knew this would save some time instead of trying to create a fully fleshed out `nim-task-runner` API and build a miraculous macro that may or may not be able to generate the needed API.
The threadpool is started by the `TaskManager` and both the `TaskManager` and the `TaskManager`'s threadpool are started as early as possible in the application lifecycle (in `nim_status_client.nim`). The `TaskManager` creates a thread to run the threadpool. During its initialization, the threadpool then spools up all the threads it will manage and puts them in an idle thread sequence. This is to prevent expensive thread creation and teardown happening during the app's lifetime as it is quite expensive and blocks the main thread. When tasks comes in to the pool, the task is sent to an idle thread, or put in a queue if all threads are busy. The idle thread is moved to the busy thread sequence. When a task is completed, the thread is taken out of the busy threads sequence and moved back in to the sequence of idle threads, effectively recycling it.
The first `spawnAndSend` we were able to change over to the new threadpool was `estimate`, which estimates the gas of a sticker purchase transaction.
From the consumer point of view, the existing `spawnAndSend` to achieve this looks like:
```nim
proc estimate*(self: StickersView, packId: int, address: string, price: string, uuid: string) {.slot.} =
let status_stickers = self.status.stickers
spawnAndSend(self, "setGasEstimate") do:
var success: bool
var estimate = status_stickers.estimateGas(packId, address, price, success)
if not success:
estimate = 325000
let result: tuple[estimate: int, uuid: string] = (estimate, uuid)
Json.encode(result)
```
And the new syntax looks like this:
```nim
proc estimate*(self: StickersView, packId: int, address: string, price: string, uuid: string) {.slot.} =
self.status.taskManager.threadPool.stickers.stickerPackPurchaseGasEstimate(cast[pointer](self.vptr), "setGasEstimate", packId, address, price, uuid)
```
The logic inside the `spawnAndSend` body was moved to [src/status/tasks/stickers.nim](https://github.com/status-im/status-desktop/compare/experiment/tasks-3?expand=1#diff-09e57eef00b0cee5c4abdb9039f948d8372e7003e09e934a9b4c7e9167d47658).
This is just the first migration of `spawnAndSend`, however moving the majority of the remaining `spawnAndSend`s will likely just be an exercise in copy/pasta. There will be one or two that may require a bit more thinking, depending how they rely on data from the model.
Once the `spawnAndSend`s have been converted to the threadpool, we can start implementing the [long-running process from the task runner use case experiments](https://github.com/status-im/nim-task-runner/blob/test/use-cases/test/use_cases/test_long_running.nim).
And finally, we can then implement the [async tasks](https://github.com/status-im/nim-task-runner/blob/test/use-cases/test/use_cases/test_async.nim) if needed.
@michaelsbradleyjr and I spent many hours digging in to the depths of nim's memory management in an attempt to understand it. We have created [a presentation with our task runner experiment findings](https://docs.google.com/presentation/d/1ItCxAfsVTcIoH_E4bgvmHljhbU-tC3T6K2A6ahwAedk/edit?usp=sharing), and @michaelsbradleyjr has spent time [answering questions off the back of that presentation.](https://gist.github.com/michaelsbradleyjr/1eaa9937b3fbb4ffff3fb814f0dd82a9).
We have created a fork of `edn.nim` at `status-im/edn.nim` and we need the PR to be merged and the commit hash updated before we can merge this PR in to `status-desktop`.
This change aligns the member list's look & feel of the community profile popup
with the designs by implementing the proper member list items styles, hover effects
and fine-tuning the context menu.
This commit also comments some of the actions provided by the context menu,
which aren't implemented yet. There's no point in having UI components that don't or
can't function.
Those will be re-introduced once they are actually implemented.
Closes#1959
We've been implementing such a button in various ways throughout the
application. Sometimes using SVG icons and rectangles, sometimes highjacking
`StyledText` components (which was clever though).
Obviously this resulted in inconsistencies, so this commit introduces
a new dedicated component to render the three-dots button for context menus.
Sometimes, `Separator` is used inside context menus to separate groups
of actions that belong together. The separator in itself doesn't have any
padding or margins in this case because the just gets transcluded as is
in the context menu, between menu items.
There are cases in the design where a padding/margin is desired though.
This change makes that possible by wrapping the separator `Rectangle` with
another `Rectangle` which controls a custom height (if desired). The inner
rectangle is then just always vertically center.
In practice this means, existing usages of `Separator` behave exactly the same,
they don't break. In addtion one can set `Separator { height: x }` while maintaining
a 1px separator line.
Since https://github.com/status-im/status-desktop/commit/93668ff75
we're hiding the setting to change appearance for compact normal mode
of the UI. For now, compact mode is the new default (reasoning is unclear
at this point).
Prior to this change, most likely many users are still using the
normal mode configuration, so we have to enforce compact mode for
those.
We've introduced a regression in https://github.com/status-im/status-desktop/commit/f1e83f74b#diff-f35edd413addd14c1f81816d6b5ee2bcbdf85fa0e3295d324cb78c98e26d4327L364 where we check whether an RPC's `error` is `null`
and its `result` is not `null`.
This breaks the application with an illegal storage access,
as in case of a successful API call, the response will have only a `result` key
(even when it's `null`).
Since we haven't done anything with a possible `error` in the reponse even before
that change was made, this commit removes the `error` check and safe guards around
whether `result` exists.
Fixes#2062
The designs for the membership request button look different now, so this
commit makes use of the `StatusSettingsLineButton` to implement that new
look & feel.
Prior to this commit, the community memberlist was represented in a nested modal
which doesn't adhere to the designs. Rather, the section should render inside the
existing modal, requiring it to be refactored using a `StackView`.
This commit refactors the community profile popup so that the different content
sections ("Overview" and "MemberList") are rendered inside of the popup and can
be pushed onto and popped off a stack view.
The content components (newly introduced in this commit) `CommunityProfilePopupMembersList`
and `CommunityProfilePopupOverview` need to define a `headerTitle`, `headerDescription` and
if needed `imageSource` so they can alter the modal's header.
The same pattern might be used in other places of the modal if required.
Partially fixes#1959
In the recent past we've improved our `ModalPopup` to allow for a bit more control
when it comes to rendering a modal footer section with call-to-action items.
The footer section is rendered when there's `footerContent` and when the height
of the footer content is > 0. This means it's possible to also hide the footer content
section from the modal, even when there's footer content children.
However, there's a separator rendered whenver the footer is visible, regardless of its
height. This results in undesired behaviour when rendering footer children of height 0.
To avoid this, this commit adjust the condition so that the separator is only rendered
when the footer visible AND the height of it is > 0.
Allow up to 5 images to be dragged and dropped in to one-on-one chats and in the timeline. Can be combined with the existing upload button. The upload file dialog has been changed to allow multiple selections. Drag and dropped images adhere to the following rules, with corresponding validations messages:
- Max 5 image
- Image size must be 0.5 MB or less
- File extension must be one of [".png", ".jpg", ".jpeg", ".heif", "tif", ".tiff"]
Drag and drop and uploaded images are now also deduplicated.
This was hardcoded into `isAdmin` for some reason. It also seems that the
property isn't actually used anywhere in the profile popup, so we might
as well get rid off it.
The `communityProfilePopup` relies on the currently `activeCommunity` to
get its data. Unfortunately, once read, even when `chatsModel.setActiveCommunity()`
is called which triggers `activeCommunityChanged`, the data in the popup
won't be updated. The next time one would open a community profile page,
it'd have the data that was previously received from the model.
This commit ensures that the popup is hydrated with the most recent data
by explicitly updating its properties right before it's opened.
Prior to this commit there was a scenario where the application would
crash due a memory bug when attempting to (re)join a community.
The scenario is as follows:
1. User creates or has been invited to community with `ON_REQUEST` permissions
2. User leaves community
3. User decides to rejoin, so she selects the community she's been part
of and hits the "Join" button
At this point Status Desktop would send a new `RequestToJoin` request, as the
community has a corresponding permissions setting.
This would then result in an `already a member` error in status-go, because
status-go checks whether the requestee is already part of the members list of
the community. The error isn't handled inside Status Desktop which causes
a crash because we're trying to access data in memory that doesn't exist.
Why is this happening?
While this might be unexpected, when leaving a community (as done on step 2 of
the mentioned scenario), users don't actually lose membership but simply
"unsubscribe" from all channels in the community in question and their `joined`
flag is set to `false`.
From that point on, re-joininng a community is done by sending a `JoinCommunity`
request (instead of `RequestToJoin`), which will then set the `joined` flag to
`true` and doesn't actually check the membership in the database.
This commit ensures we're calling the right API by checking whether not only
whether the community is needs `ON _REQUEST` permissions, but also whether the
user isn't already a member of it.
Fixes#2017