mirror of
https://github.com/MyLuxy/overdrive.git
synced 2026-10-17 19:01:08 +02:00
94 lines
4.0 KiB
C++
94 lines
4.0 KiB
C++
// Copyright (C) 2026 half144 and PS5 React contributors
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Additional attribution term: see LICENSE-ATTRIBUTION.
|
|
#include "js_heap.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
|
|
extern "C" {
|
|
#include "er_js_alloc.h"
|
|
}
|
|
|
|
namespace {
|
|
const JSMallocFunctions* inner = nullptr;
|
|
std::size_t live = 0;
|
|
|
|
void* counted(void* ptr) {
|
|
if (ptr) live += inner->js_malloc_usable_size(ptr);
|
|
return ptr;
|
|
}
|
|
|
|
void* js_calloc(void* opaque, std::size_t count, std::size_t size) { return counted(inner->js_calloc(opaque, count, size)); }
|
|
void* js_malloc(void* opaque, std::size_t size) { return counted(inner->js_malloc(opaque, size)); }
|
|
|
|
void js_free(void* opaque, void* ptr) {
|
|
if (ptr) live -= inner->js_malloc_usable_size(ptr);
|
|
inner->js_free(opaque, ptr);
|
|
}
|
|
|
|
void* js_realloc(void* opaque, void* ptr, std::size_t size) {
|
|
const std::size_t before = ptr ? inner->js_malloc_usable_size(ptr) : 0;
|
|
void* moved = inner->js_realloc(opaque, ptr, size);
|
|
// A failed realloc leaves the block as it was; a zero size frees it.
|
|
if (moved || size == 0) live = live - before + (moved ? inner->js_malloc_usable_size(moved) : 0);
|
|
return moved;
|
|
}
|
|
|
|
std::size_t usable_size(const void* ptr) { return inner->js_malloc_usable_size(ptr); }
|
|
|
|
JSMallocFunctions functions = {js_calloc, js_malloc, js_free, js_realloc, usable_size};
|
|
|
|
// No idle collection right after input: the focus scroll it started is still moving.
|
|
constexpr std::int64_t kIdleUs = 500000;
|
|
// Below this much garbage a collection is not worth a frame.
|
|
constexpr std::size_t kMinGarbage = 512 * 1024;
|
|
// Still frames before an idle collection (GcScheduler::wait_for_still). A focus ring's sweep repaints
|
|
// its edges every frame and stays under the bound.
|
|
constexpr int kStillFrames = 3;
|
|
// The room after a collection: QuickJS's 1.5 times what survived, but at least this much, so a burst of
|
|
// input (React allocates on every key) does not reach the threshold before an idle frame comes.
|
|
constexpr std::size_t kMinRoom = 8 * 1024 * 1024;
|
|
|
|
constexpr std::size_t kCeiling = kJsMemoryLimit - kJsMemoryLimit / 8;
|
|
|
|
double mib(std::size_t bytes) { return bytes / (1024.0 * 1024.0); }
|
|
} // namespace
|
|
|
|
const JSMallocFunctions* js_heap_functions() {
|
|
inner = er_js_default_malloc_functions();
|
|
return &functions;
|
|
}
|
|
|
|
const char* GcScheduler::frame(JSRuntime* runtime, std::int64_t now_us, std::size_t repainted_px,
|
|
std::size_t screen_px) {
|
|
still_frames_ = repainted_px * 50 < screen_px ? still_frames_ + 1 : 0;
|
|
const std::size_t threshold = JS_GetGCThreshold(runtime);
|
|
if (threshold != threshold_) {
|
|
// QuickJS collected while the frame ran and moved its threshold to 1.5 times what survived.
|
|
const bool first = threshold_ == 0;
|
|
threshold_ = std::min(std::max(threshold, live + kMinRoom), kCeiling);
|
|
if (threshold_ != threshold) JS_SetGCThreshold(runtime, threshold_);
|
|
live_after_gc_ = live;
|
|
if (first) return nullptr;
|
|
std::snprintf(line_, sizeof line_, "gc: automatic, %.1f MiB live, next at %.1f MiB", mib(live), mib(threshold_));
|
|
return line_;
|
|
}
|
|
const std::size_t room = threshold > live_after_gc_ ? threshold - live_after_gc_ : 0;
|
|
const std::size_t garbage = live > live_after_gc_ ? live - live_after_gc_ : 0;
|
|
if (now_us - last_input_us_ < kIdleUs || garbage < kMinGarbage || garbage < room / 3) return nullptr;
|
|
// Waiting for a still screen stops once three quarters of the room is garbage: QuickJS would soon
|
|
// collect inside an allocation anyway, likely in the frame that handles the next key.
|
|
if (wait_for_still_ && still_frames_ < kStillFrames && garbage < room / 4 * 3) return nullptr;
|
|
const std::size_t before = live;
|
|
const std::int64_t start = clock_();
|
|
JS_RunGC(runtime);
|
|
live_after_gc_ = live;
|
|
// QuickJS's own rule after a collection, with the room's floor.
|
|
threshold_ = std::min(live + std::max(live / 2, kMinRoom), kCeiling);
|
|
JS_SetGCThreshold(runtime, threshold_);
|
|
std::snprintf(line_, sizeof line_, "gc: idle, %.1f ms, %.1f -> %.1f MiB live", (clock_() - start) / 1000.0,
|
|
mib(before), mib(live));
|
|
return line_;
|
|
}
|