1111f691c6
Co-authored-by: n.fedorov <mail@nfedorov.dev> Co-committed-by: n.fedorov <mail@nfedorov.dev>
7.2 KiB
7.2 KiB
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 ondomain/.service/depends ondomain/+infra/.ui/depends only onservice/+domain/— must not include<arpa/inet.h>,<sys/socket.h>,<pthread.h>, or callrecv/senddirectly.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.
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::atomicmembers inTransferServiceuse sequential-consistency (default). No explicitmemory_orderneeded at this concurrency level. TransferState::statusis astd::stringprotected bystd::mutex status_mutex. All otherTransferStatefields are atomics.pthread_create+pthread_detachis used throughout (libnx's supported path). Threads are never joined — they signal completion viastate.done = trueand 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
# 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.