Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed archive.zip
Binary file not shown.
97 changes: 0 additions & 97 deletions native_crash.log

This file was deleted.

8 changes: 6 additions & 2 deletions osu.Android/CrashDiagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ internal static class CrashDiagnostics
/// <param name="context">Any <see cref="Context"/> — typically the host Activity.</param>
public static void InstallNativeHandler(Context context)
{
// Idempotent at the managed level: the native handler dedupes via its own
// g_installed flag, but we also avoid re-writing the sentinel and re-running
// the directory-resolution / P-Invoke path on repeat calls.
if (Interlocked.Exchange(ref initialised, 1) != 0)
return;

try
{
resolveDirs(context);
Expand Down Expand Up @@ -106,8 +112,6 @@ public static void InstallNativeHandler(Context context)
{
Debug.WriteLine($"[osu!] CrashDiagnostics.InstallNativeHandler outer failure: {e.Message}");
}

Interlocked.Exchange(ref initialised, 1);
}

/// <summary>
Expand Down
23 changes: 23 additions & 0 deletions osu.Android/Native/oboe_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ bool OboeBridge::start() {
}

void OboeBridge::stop() {
// Signal any in-flight error-callback recovery (onErrorAfterClose →
// reopenAndRestart) to bail out, so the bridge cannot be reopened from
// Oboe's internal thread while we are tearing it down from .NET.
disposing_.store(true);
active_.store(false);

std::lock_guard<std::mutex> lock(streamLock_);
Expand Down Expand Up @@ -360,6 +364,16 @@ void OboeBridge::onErrorAfterClose(oboe::AudioStream* stream, oboe::Result error
oboe::convertToText(error));
active_.store(false);

// Bail out immediately if a teardown is in flight: stop() has signalled
// that the bridge is being destroyed by .NET, and proceeding with the
// recovery path could leave us inside open()/requestStart() while the
// OboeBridge object is freed by the destructor.
if (disposing_.load()) {
std::lock_guard<std::mutex> lock(streamLock_);
stream_.reset();
return;
}

if (error == oboe::Result::ErrorDisconnected) {
{
std::lock_guard<std::mutex> lock(streamLock_);
Expand All @@ -378,9 +392,18 @@ void OboeBridge::onErrorAfterClose(oboe::AudioStream* stream, oboe::Result error
}

bool OboeBridge::reopenAndRestart() {
// Re-check teardown after acquiring no-lock fast path: stop() may have
// been called between onErrorAfterClose's check and now.
if (disposing_.load()) return false;

if (open(requestedSampleRate_)) {
std::lock_guard<std::mutex> lock(streamLock_);

if (disposing_.load()) {
stream_.reset();
return false;
}

if (stream_) {
oboe::Result result = stream_->requestStart();

Expand Down
1 change: 1 addition & 0 deletions osu.Android/Native/oboe_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class OboeBridge : public oboe::AudioStreamCallback {

mutable std::mutex streamLock_;
std::atomic<bool> active_{false};
std::atomic<bool> disposing_{false};
std::atomic<double> latencyMs_{-1.0};
std::atomic<uint32_t> callbackCount_{0};
std::atomic<OboeAudioProvider> provider_{nullptr};
Expand Down
9 changes: 6 additions & 3 deletions osu.Android/Native/vulkan_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,12 @@ bool VulkanProbe::createInstance() {

bool VulkanProbe::queryDevice() {
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance_, &deviceCount, nullptr);
if (vkEnumeratePhysicalDevices(instance_, &deviceCount, nullptr) != VK_SUCCESS) return false;
if (deviceCount == 0) return false;

std::vector<VkPhysicalDevice> devices(deviceCount);
if (vkEnumeratePhysicalDevices(instance_, &deviceCount, devices.data()) != VK_SUCCESS) return false;
if (deviceCount == 0) return false;

VkPhysicalDevice selected = devices[0];
for (const auto& dev : devices) {
Expand Down Expand Up @@ -131,6 +132,7 @@ void VulkanProbe::queryQueueFamilies(VkPhysicalDevice device) {
uint32_t count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &count, nullptr);
deviceInfo_.queueFamilyCount = count;
if (count == 0) return;
std::vector<VkQueueFamilyProperties> families(count);
vkGetPhysicalDeviceQueueFamilyProperties(device, &count, families.data());
for (const auto& family : families) {
Expand All @@ -143,9 +145,10 @@ void VulkanProbe::queryQueueFamilies(VkPhysicalDevice device) {

void VulkanProbe::queryModernExtensions(VkPhysicalDevice device) {
uint32_t count = 0;
vkEnumerateDeviceExtensionProperties(device, nullptr, &count, nullptr);
if (vkEnumerateDeviceExtensionProperties(device, nullptr, &count, nullptr) != VK_SUCCESS) return;
if (count == 0) return;
std::vector<VkExtensionProperties> exts(count);
vkEnumerateDeviceExtensionProperties(device, nullptr, &count, exts.data());
if (vkEnumerateDeviceExtensionProperties(device, nullptr, &count, exts.data()) != VK_SUCCESS) return;
for (const auto& ext : exts) {
if (strcmp(ext.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) deviceInfo_.supportsSwapchain = true;
if (strcmp(ext.extensionName, VK_KHR_PRESENT_ID_EXTENSION_NAME) == 0) deviceInfo_.supportsPresentId = true;
Expand Down
2 changes: 1 addition & 1 deletion osu.Android/OsuGameAndroid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public partial class OsuGameAndroid : OsuGame
private object? nativeBridges;

/// <summary>
/// Last value passed to <see cref="OsuGameActivity.RequestedOrientation"/> by
/// Last value passed to <see cref="global::Android.App.Activity.RequestedOrientation"/> by
/// <see cref="updateOrientation"/>. Cached locally so we can short-circuit
/// redundant updates without round-tripping through the activity getter, which
/// itself performs a binder IPC on modern Android.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,42 @@ protected override void Update()
double latestValidTime = clock.CurrentTime;
double earliestTimeValid = latestValidTime - 1000 * gameplayClock.GetTrueGameplayRate();

// Timestamps are added in chronological order (from clock.CurrentTime),
// so we can use binary-search-style trimming instead of per-element RemoveAt.
// Timestamps are appended at clock.CurrentTime which is *usually* monotonic, but
// gameplay rewinds (and replay seeks) can append a smaller value after a larger
// one — so the list is not strictly sorted. We still scan from the end (where
// newly-appended entries live) to match the access pattern of the previous
// implementation, but we cannot stop early on either bound because an older
Comment on lines +39 to +43
// out-of-order entry may live anywhere in the list.

// First pass: drop any timestamps now in the future (caused by rewinding).
// Walk backwards and shift surviving entries down in-place; this is O(n) and
// avoids the O(n²) RemoveAt-in-loop pattern of the original code.
Comment on lines +41 to +48
int write = 0;

for (int read = 0; read < timestamps.Count; read++)
{
double t = timestamps[read];

if (t > latestValidTime)
continue;
Comment on lines +52 to +56

// Trim future timestamps caused by rewinding (remove from the end in one batch).
// RemoveRange from the end is a single operation vs repeated RemoveAt calls.
int trimStart = timestamps.Count;
if (write != read)
timestamps[write] = t;

while (trimStart > 0 && timestamps[trimStart - 1] > latestValidTime)
trimStart--;
write++;
}

if (trimStart < timestamps.Count)
timestamps.RemoveRange(trimStart, timestamps.Count - trimStart);
if (write < timestamps.Count)
timestamps.RemoveRange(write, timestamps.Count - write);

// Count timestamps within the valid 1-second window.
// Since the list is in chronological order, scan backwards until we leave the window.
// Count entries inside the 1-second window. Cannot break early because the list
// is not guaranteed sorted (see above), so scan all surviving timestamps.
int count = 0;

for (int i = timestamps.Count - 1; i >= 0; i--)
for (int i = 0; i < timestamps.Count; i++)
{
if (timestamps[i] < earliestTimeValid)
break;

count++;
if (timestamps[i] >= earliestTimeValid)
count++;
}

Value = count;
Expand Down
Loading