-
-
Notifications
You must be signed in to change notification settings - Fork 681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
an Ebitengine application hangs when the app is launched via a Steam client #3181
Comments
This doesn't seem to be an Ebitengine specific issue |
By the way, did you specify |
Yes, the problem also reproduces with |
Hmm, wouldn't rebooting your machine change the situation? |
I just tested to be sure. Unfortunately, rebooting does not change the situation. I am still able to reproduce this in the noise example with the above steps. |
I could reproduce this by replacing an exe with noise's exe and staring the game via the Steam client! |
I saw the GC stats and apparently freezing is not counted as GC time. diff --git a/examples/noise/main.go b/examples/noise/main.go
index 0bf33c55f..f65e4e9c0 100644
--- a/examples/noise/main.go
+++ b/examples/noise/main.go
@@ -18,6 +18,7 @@ import (
"fmt"
"image"
"log"
+ "runtime/debug"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
@@ -57,12 +58,17 @@ func (g *Game) Update() error {
g.noiseImage.Pix[4*i+2] = uint8(x >> 8)
g.noiseImage.Pix[4*i+3] = 0xff
}
+ _ = make([]byte, 256*1024)
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
screen.WritePixels(g.noiseImage.Pix)
ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f\nFPS: %0.2f", ebiten.ActualTPS(), ebiten.ActualFPS()))
+
+ var gcStats debug.GCStats
+ debug.ReadGCStats(&gcStats)
+ ebitenutil.DebugPrint(screen, fmt.Sprintf("\n\nLastGC: %s\nNumGC: %d\nPauseTotal: %s", gcStats.LastGC, gcStats.NumGC, gcStats.PauseTotal))
} |
I think I could reproduce the freezing even without Ebitengine package main
import (
"runtime"
"syscall"
"unsafe"
)
const (
WS_OVERLAPPEDWINDOW = 0x00000000 | 0x00C00000 | 0x00080000 | 0x00040000 | 0x00020000 | 0x00010000
CW_USEDEFAULT = ^0x7fffffff
SW_SHOW = 5
WM_DESTROY = 2
)
type (
ATOM uint16
HANDLE uintptr
HINSTANCE HANDLE
HICON HANDLE
HCURSOR HANDLE
HBRUSH HANDLE
HWND HANDLE
HMENU HANDLE
)
type WNDCLASSEX struct {
Size uint32
Style uint32
WndProc uintptr
ClsExtra int32
WndExtra int32
Instance HINSTANCE
Icon HICON
Cursor HCURSOR
Background HBRUSH
MenuName *uint16
ClassName *uint16
IconSm HICON
}
type RECT struct {
Left, Top, Right, Bottom int32
}
type POINT struct {
X, Y int32
}
type MSG struct {
Hwnd HWND
Message uint32
WParam uintptr
LParam uintptr
Time uint32
Pt POINT
}
func GetModuleHandle(modulename *uint16) HINSTANCE {
r, _, _ := syscall.SyscallN(procGetModuleHandle.Addr(), uintptr(unsafe.Pointer(modulename)))
return HINSTANCE(r)
}
func RegisterClassEx(w *WNDCLASSEX) ATOM {
r, _, _ := syscall.SyscallN(procRegisterClassEx.Addr(), uintptr(unsafe.Pointer(w)))
return ATOM(r)
}
func CreateWindowEx(exStyle uint, className, windowName *uint16,
style uint, x, y, width, height int, parent HWND, menu HMENU,
instance HINSTANCE, param unsafe.Pointer) HWND {
r, _, _ := syscall.SyscallN(procCreateWindowEx.Addr(), uintptr(exStyle), uintptr(unsafe.Pointer(className)),
uintptr(unsafe.Pointer(windowName)), uintptr(style), uintptr(x), uintptr(y), uintptr(width), uintptr(height),
uintptr(parent), uintptr(menu), uintptr(instance), uintptr(param))
return HWND(r)
}
func AdjustWindowRect(rect *RECT, style uint, menu bool) bool {
var iMenu uintptr
if menu {
iMenu = 1
}
r, _, _ := syscall.SyscallN(procAdjustWindowRect.Addr(), uintptr(unsafe.Pointer(rect)), uintptr(style), iMenu)
return r != 0
}
func ShowWindow(hwnd HWND, cmdshow int) bool {
r, _, _ := syscall.SyscallN(procShowWindow.Addr(), uintptr(hwnd), uintptr(cmdshow))
return r != 0
}
func GetMessage(msg *MSG, hwnd HWND, msgFilterMin, msgFilterMax uint32) int {
r, _, _ := syscall.SyscallN(procGetMessage.Addr(), uintptr(unsafe.Pointer(msg)), uintptr(hwnd), uintptr(msgFilterMin), uintptr(msgFilterMax))
return int(r)
}
func TranslateMessage(msg *MSG) bool {
r, _, _ := syscall.SyscallN(procTranslateMessage.Addr(), uintptr(unsafe.Pointer(msg)))
return r != 0
}
func DispatchMessage(msg *MSG) uintptr {
r, _, _ := syscall.SyscallN(procDispatchMessage.Addr(), uintptr(unsafe.Pointer(msg)))
return r
}
func DefWindowProc(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr {
r, _, _ := syscall.SyscallN(procDefWindowProc.Addr(), uintptr(hwnd), uintptr(msg), wParam, lParam)
return r
}
func PostQuitMessage(exitCode int) {
syscall.SyscallN(procPostQuitMessage.Addr(), uintptr(exitCode))
}
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetModuleHandle = kernel32.NewProc("GetModuleHandleW")
user32 = syscall.NewLazyDLL("user32.dll")
procRegisterClassEx = user32.NewProc("RegisterClassExW")
procCreateWindowEx = user32.NewProc("CreateWindowExW")
procAdjustWindowRect = user32.NewProc("AdjustWindowRect")
procShowWindow = user32.NewProc("ShowWindow")
procGetMessage = user32.NewProc("GetMessageW")
procTranslateMessage = user32.NewProc("TranslateMessage")
procDispatchMessage = user32.NewProc("DispatchMessageW")
procDefWindowProc = user32.NewProc("DefWindowProcW")
procPostQuitMessage = user32.NewProc("PostQuitMessage")
)
func init() {
runtime.LockOSThread()
}
func main() {
className, err := syscall.UTF16PtrFromString("Sample Window Class")
if err != nil {
panic(err)
}
inst := GetModuleHandle(className)
wc := WNDCLASSEX{
Size: uint32(unsafe.Sizeof(WNDCLASSEX{})),
WndProc: syscall.NewCallback(wndProc),
Instance: inst,
ClassName: className,
}
RegisterClassEx(&wc)
wr := RECT{
Left: 0,
Top: 0,
Right: 320,
Bottom: 240,
}
title, err := syscall.UTF16PtrFromString("My Title")
if err != nil {
panic(err)
}
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, false)
hwnd := CreateWindowEx(
0, className,
title,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, int(wr.Right-wr.Left), int(wr.Bottom-wr.Top),
0, 0, inst, nil,
)
if hwnd == 0 {
panic(syscall.GetLastError())
}
ShowWindow(hwnd, SW_SHOW)
go func() {
for {
_ = make([]byte, 256*1024)
time.Sleep(time.Millisecond)
}
}()
var msg MSG
for GetMessage(&msg, 0, 0, 0) != 0 {
TranslateMessage(&msg)
DispatchMessage(&msg)
}
}
func wndProc(hwnd HWND, msg uint32, wparam, lparam uintptr) uintptr {
switch msg {
case WM_DESTROY:
PostQuitMessage(0)
}
return DefWindowProc(hwnd, msg, wparam, lparam)
} |
I could reproduce this even as a simple console game golang/go#71242 (comment) |
@corfe83 Could you try adding this to your game? func init() {
if runtime.GOOS == "windows" && os.Getenv("SteamClientLaunch") == "1" {
runtime.GOMAXPROCS(max(1, min(2, runtime.NumCPU()-1)))
}
} |
@hajimehoshi I just tested adding this code to the game. It does not help, unfortunately. |
Hmm, what about runtime.GOMAXPROCS(1)? |
The problem also reproduces with GOMAXPROCS(1). I'm using ebiten single thread mode. |
What if you do not use the single thread mode? |
Multi thread mode with GOMAXPROCS(1) still reproduces the problem as well. |
Related: https://steamcommunity.com/groups/SteamClientBeta/discussions/3/4206993388791543748/ Changing the permission might resolve the issue, but this is too hacky. |
Would manual GCs causes the issue? For example: debug.SetGCPercent(-1)
go func() {
for {
runtime.GC()
time.Sleep(time.Second)
}
}() |
As we discussed in the Discord server, please try |
I can confirm this resolves the issue. I'm so glad to see a workaround we can use to prevent this issue! |
Awesome! I'll update the blog article about Steam https://ebitengine.org/en/blog/steam.html to notify this later. After that, I'll close this since there is nothing we can do in Ebitengine for this issue unfortunately. My current understanding is that the Steam hooking does fishy things, so I have to prove this by creating a minimized test case in C. |
Yes, closing once documented seems reasonable to me! |
Ebitengine Version
2.8.6
Operating System
Go Version (
go version
)1.23.4
What steps will reproduce the problem?
debug.SetGCPercent(5)
What is the expected result?
Noise.exe runs smoothly inside of steam, just like outside of it
What happens instead?
It hangs or freezes for 10+ seconds
Anything else you feel useful to add?
I have AMD Ryzen 5800X, 32GB of RAM, and an nvidia 3080 RTX GPU, this shouldn't be slow. This happens with both OpenGL and DirectX.
You can use environment variables to play with GOGC to either make GC run more frequently, or never runs. If you disable GC it will never happen, and if you set GOGC to something like 5 so GC runs very often, it happens very frequently. It does NOT happen every time GC runs, it only happens sometimes.
I suspect that any ebiten executable that runs GC with steam overlay will be affected.
The text was updated successfully, but these errors were encountered: