1111f691c6
Co-authored-by: n.fedorov <mail@nfedorov.dev> Co-committed-by: n.fedorov <mail@nfedorov.dev>
164 lines
7.2 KiB
Markdown
164 lines
7.2 KiB
Markdown
# NXST Architecture
|
|
|
|
## Layer Diagram
|
|
|
|
```
|
|
┌──────────────────────────────┐
|
|
│ ui/ │
|
|
│ TitlesLayout, UsersLayout │
|
|
│ TransferOverlay, HeaderBar │
|
|
└──────────────┬───────────────┘
|
|
│ calls
|
|
┌──────────────▼───────────────┐
|
|
│ service/ │
|
|
│ TransferService │
|
|
└──────┬───────────────┬───────┘
|
|
│ │
|
|
┌────────────▼──┐ ┌───────▼────────┐
|
|
│ infra/net/ │ │ infra/fs/ │
|
|
│ TransferService│ │ io, directory │
|
|
│ (sender thread │ │ filesystem │
|
|
│ recv threads) │ │ handles │
|
|
└────────────┬──┘ └───────┬────────┘
|
|
│ │
|
|
┌────────────▼───────────────▼────────┐
|
|
│ domain/ │
|
|
│ Title, Account, TransferState │
|
|
│ Result<T>, protocol constants │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
┌────────────────────▼────────────────┐
|
|
│ libnx, Plutonium, SDL2, devkitpro│
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
**Hard layering rules:**
|
|
- `domain/` depends on nothing in the project.
|
|
- `infra/` depends only on `domain/`.
|
|
- `service/` depends on `domain/` + `infra/`.
|
|
- `ui/` depends only on `service/` + `domain/` — must not include `<arpa/inet.h>`, `<sys/socket.h>`, `<pthread.h>`, or call `recv`/`send` directly.
|
|
- `app/` (MainApplication, main.cpp) wires everything together.
|
|
|
|
---
|
|
|
|
## Key Types
|
|
|
|
### `nxst::TransferService` (`include/nxst/service/transfer_service.hpp`)
|
|
|
|
Owns all network state for both transfer directions. One instance lives in `MainApplication`.
|
|
|
|
| Method | Description |
|
|
|--------|-------------|
|
|
| `startSend(index, uid)` | Backup save, discover receiver, stream files |
|
|
| `startReceive(index, uid, title_name)` | Listen for sender, receive files, restore save |
|
|
| `cancelSend()` / `cancelReceive()` | Interrupt in-flight transfer (socket shutdown) |
|
|
| `isSendDone()` / `isReceiveDone()` | Polled by UI render loop |
|
|
| `restoreSucceeded()` / `restoreError()` | Restore outcome after receive |
|
|
|
|
**Threading model:**
|
|
|
|
```
|
|
UI thread (Plutonium event loop)
|
|
└─ startSend()
|
|
└─ senderEntry [pthread, detached]
|
|
├─ findServer() — UDP multicast, 100 ms poll slices
|
|
├─ io::backup() — creates local save backup
|
|
└─ TCP send loop
|
|
|
|
└─ startReceive()
|
|
├─ broadcastEntry [pthread, detached] — UDP multicast listener
|
|
└─ acceptEntry [pthread, detached]
|
|
├─ TCP receive loop
|
|
└─ io::restore() — mounts save, clears, copies, commits
|
|
```
|
|
|
|
Cancellation: `cancelSend()` / `cancelReceive()` call `shutdown(SHUT_RDWR)` on all open sockets. Blocking `read`/`recvfrom`/`accept` return errors immediately. Atomic flags are checked at loop boundaries.
|
|
|
|
### `nxst::Result<T, E>` (`include/nxst/domain/result.hpp`)
|
|
|
|
85-line tagged-union result type. No exceptions, no `std::variant`. Used for `io::backup` and `io::restore` return values.
|
|
|
|
```cpp
|
|
auto result = io::backup(title_index, uid);
|
|
if (!result.isOk()) {
|
|
failSend("Backup failed: " + result.error());
|
|
return;
|
|
}
|
|
fs::path dir = result.value();
|
|
```
|
|
|
|
`Result<void, E>` specialisation available for operations that succeed without a value.
|
|
|
|
### `nxst::FsFileSystemHandle` / `nxst::FileHandle` (`include/nxst/infra/fs/handles.hpp`)
|
|
|
|
RAII wrappers ensuring `fsFsClose` / `fclose` are called on all exit paths.
|
|
|
|
---
|
|
|
|
## Threading Invariants
|
|
|
|
- All `std::atomic` members in `TransferService` use sequential-consistency (default). No explicit `memory_order` needed at this concurrency level.
|
|
- `TransferState::status` is a `std::string` protected by `std::mutex status_mutex`. All other `TransferState` fields are atomics.
|
|
- `pthread_create` + `pthread_detach` is used throughout (libnx's supported path). Threads are never joined — they signal completion via `state.done = true` and set their active flag to false.
|
|
- Cancel is safe to call from any thread.
|
|
|
|
---
|
|
|
|
## File Map
|
|
|
|
```
|
|
include/nxst/
|
|
├── app/
|
|
│ ├── main.hpp — global state (g_currentTime, g_sortMode, …)
|
|
│ └── main_application.hpp — MainApplication : pu::ui::Application
|
|
├── domain/
|
|
│ ├── account.hpp — AccountUid, Account::ids(), Account::username()
|
|
│ ├── common.hpp — StringUtils (UTF-8/16, elide, accents)
|
|
│ ├── protocol.hpp — wire constants (ports, sentinel, buffer size)
|
|
│ ├── result.hpp — Result<T, E> tagged union
|
|
│ ├── title.hpp — Title, getTitle(), getTitleCount()
|
|
│ ├── transfer_state.hpp — TransferState (atomics + mutex-guarded status)
|
|
│ └── util.hpp — StringUtils helpers, blinkLed
|
|
├── infra/
|
|
│ ├── fs/
|
|
│ │ ├── directory.hpp — Directory iterator (libnx FsDir)
|
|
│ │ ├── filesystem.hpp — FileSystem::mount/unmount wrappers
|
|
│ │ ├── handles.hpp — FsFileSystemHandle, FileHandle (RAII)
|
|
│ │ └── io.hpp — io::backup, io::restore, io::copyFile, …
|
|
│ ├── net/
|
|
│ │ └── socket.hpp — Socket RAII wrapper (int fd)
|
|
│ └── sys/
|
|
│ └── logger.hpp — nxst::log::info/warn/error (printf-checked)
|
|
├── service/
|
|
│ └── transfer_service.hpp — TransferService
|
|
└── ui/
|
|
├── card.hpp — Card UI component
|
|
├── const.h — layout/color/font constants
|
|
├── header_bar.hpp — HeaderBar (title + user avatar row)
|
|
├── hint_bar.hpp — HintBar (button legend)
|
|
├── theme.hpp — color, radius, spacing, type tokens
|
|
├── titles_layout.hpp — TitlesLayout
|
|
├── transfer_overlay.hpp — TransferOverlay (progress modal)
|
|
├── ui_context.hpp — UiContext (selected user state)
|
|
└── users_layout.hpp — UsersLayout
|
|
```
|
|
|
|
---
|
|
|
|
## Build
|
|
|
|
```bash
|
|
# Configure (once — toolchain preset reads $DEVKITPRO automatically)
|
|
cmake --preset switch
|
|
|
|
# Build
|
|
cmake --build build
|
|
|
|
# Send to Switch via nxlink
|
|
cmake --build build --target send
|
|
```
|
|
|
|
Output: `build/NXST.nro`
|
|
|
|
See `README.md` for full build prerequisites.
|