* feat(dispatcher): replace Application.Current.Dispatcher with Avalonia UIThread
Migrate all Application.Current.Dispatcher.Invoke() and BeginInvoke()
calls to Avalonia.Threading.Dispatcher.UIThread equivalents across the
three remaining affected files. CustomRichTextBox.cs was already migrated.
AudioPlayerViewModel.cs (7 calls):
- Remove 'using System.Windows;' (only used for Application)
- Add 'using Avalonia.Threading;'
- Replace all Application.Current.Dispatcher.Invoke(() => {...}) with
Dispatcher.UIThread.Post(() => {...}) in Load(), AddToPlaylist(x2),
Remove(), Replace(), SavePlaylist(), Dispose().
Post() is appropriate because all callers are fire-and-forget
cross-thread UI collection mutations.
CUE4ParseViewModel.cs (3 calls):
- Remove 'using Application = System.Windows.Application;'
- Add 'using Avalonia.Threading;'
- SnooperViewer getter: extract local function MakeSnooper() and use
Dispatcher.UIThread.CheckAccess() ? MakeSnooper() :
Dispatcher.UIThread.InvokeAsync(MakeSnooper).GetAwaiter().GetResult()
Avoids deadlock when accessed from UI thread (MenuCommand), while
still dispatching correctly when called from background thread.
- FindReferences(): Dispatcher.UIThread.Post(delegate {...})
- Audio player window: Dispatcher.UIThread.Post(delegate {...}) and
remove the stale TODO comment (Post already solves the thread-lifetime
issue the comment described).
Snooper.cs (1 call):
- Keep 'using Application = System.Windows.Application;' — still
needed for Application.GetResourceStream in LoadWindowIcon().
- Add 'using Avalonia.Threading;'
- Run(): Dispatcher.UIThread.Post() so the background thread is not
blocked for the lifetime of the game loop window.
Closes #25
* fix(dispatcher): address review findings and complete migration
Fix [C1] — Load() wrapped in Post() silently broke every Load→Play call:
All callers of Load() are already on the UI thread (ICommand.Execute,
key handlers, or inside an enclosing Post lambda). Wrapping Load() in
Post() deferred it past the immediately-following Play() call, so Play()
always found _soundOut == null. Fix: remove the Post wrapper and run
Load() synchronously. The WPF Dispatcher.Invoke was a no-op re-entry
from the dispatcher thread anyway; Post() does not have that property.
Fix [M1] — Dispose() as Post() risked leaked resources and a timer race:
(a) _sourceTimer ticks every 10 ms on a threadpool thread. With deferred
disposal, there was a window many ticks wide where _waveSource was being
disposed concurrently with a tick accessing it (use-after-dispose).
(b) If the Avalonia event loop shut down before processing the queued
Post (e.g. last window closing), _waveSource/_soundOut would never be
disposed. Fix: stop the timer synchronously with Change(Infinite,Infinite)
before touching any audio resources; then dispose inline (Dispose() is
always called from OnClosing, which fires on the UI thread).
Fix [M2] — Snooper.Run() was posting the blocking GLFW game loop to the
Avalonia UI thread, freezing the entire UI for the lifetime of the 3D
viewer. The Win32 shared message-pump trick that made this work on WPF
does not apply to Avalonia on Linux. Fix: post only IsVisible=true to
the UI thread (GLFW requires glfwShowWindow on the main/creating thread),
set GLFW.SetWindowShouldClose (thread-safe per GLFW docs) from the
calling thread, then start base.Run() on a new IsBackground thread named
'Snooper-GameLoop'. OpenTK transfers the GL context to that thread via
Context.MakeCurrent() at the start of Run().
Fix [m1] — SnooperViewer getter had no lock: two concurrent background
threads could both see _snooper==null, both InvokeAsync MakeSnooper,
and the second call would overwrite and leak the first Snooper. Fix:
volatile field + double-checked lock (_snooperLock) around the slow
path. The CheckAccess() fast path (UI thread caller) never touches the
lock so there is no deadlock.
Expand scope to all remaining Application.Current.Dispatcher usages
(addresses reviewer comment on PR #78):
- BackupManagerViewModel.cs (1 usage → Post)
- AssetsFolderViewModel.cs (CheckAccess+Invoke pattern → CheckAccess+
InvokeAsync.GetAwaiter().GetResult(); BulkPopulate → Post)
- TabControlViewModel.cs (8 usages → Invoke for synchronous ops)
- GameDirectoryViewModel.cs (1 usage → Post)
- Renderer.cs (3 usages → Invoke)
- EndpointEditor.xaml.cs (1 usage → Post)
GameFileViewModel.cs is intentionally excluded: its Dispatcher usage is
inseparable from System.Windows.Media.ImageSource/BitmapImage types
that require a separate migration ticket.
* fix(snooper): dispatch IsVisible to UI thread from game loop thread
After [M2] moved base.Run() off the UI thread to Snooper-GameLoop,
all game loop callbacks — OnClosing, key/button handlers, animation
freeze points — fire on that background thread, making every IsVisible
assignment a thread safety violation (glfwShowWindow and glfwHideWindow
must be called from the GLFW main thread, which is the Avalonia UI
thread that created the window).
GLFW.SetWindowShouldClose remains inline in both WindowShouldClose and
WindowShouldFreeze: it is explicitly documented as callable from any
thread.
IsVisible is now assigned inside Dispatcher.UIThread.Post() in both
methods. Post (not Invoke) is correct: no caller checks or depends on
the visibility change completing synchronously.
|
||
|---|---|---|
| .github | ||
| .vscode | ||
| CUE4Parse@06fbf1aced | ||
| FModel | ||
| .editorconfig | ||
| .gitignore | ||
| .gitmodules | ||
| LICENSE | ||
| NOTICE | ||
| README.md | ||
FModel Linux — Unreal Engine Archives Explorer for Linux
This is an unofficial Linux port of FModel, originally created by Asval (4sval) and contributors. It is not affiliated with or endorsed by the upstream project or its maintainers. Please do not report Linux-port-specific issues upstream.
Description
FModel Linux is a Linux port of FModel — an archive explorer for Unreal Engine games. It uses CUE4Parse as its core parsing library, providing robust support for the latest UE4 and UE5 archive formats, along with a comprehensive set of tools for previewing and converting game packages.
This fork replaces the Windows-only WPF UI stack with Avalonia UI and removes other Windows-specific dependencies to enable native Linux support. It is maintained by r6e.
Installation
Installation instructions for the Linux port are available in the project wiki (work in progress).
For the upstream Windows release, refer to the official FModel installation guide.
Supporting the Upstream Project
This fork does not accept donations. If you find FModel valuable, please consider supporting the original FModel project and its contributors.
License
FModel Linux is a derivative work licensed under GPL-3. The original FModel project is copyright © Asval and FModel contributors. Licenses of third-party libraries used are listed in NOTICE.