diff --git a/internal/debug/caller.go b/internal/debug/caller.go new file mode 100644 index 000000000..adc8465e0 --- /dev/null +++ b/internal/debug/caller.go @@ -0,0 +1,67 @@ +// Copyright 2024 The Ebitengine Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package debug + +import ( + "fmt" + "os/exec" + "path" + "path/filepath" + "runtime" + "strings" + "sync" +) + +var ( + ebitengineFileDir string + ebitengineFileDirOnce sync.Once +) + +// FirstCaller returns the file and line number of the first caller outside of Ebitengine. +func FirstCaller() (file string, line int, ok bool) { + ebitengineFileDirOnce.Do(func() { + cmd := exec.Command("go", "list", "-f", "{{.Dir}}", "github.com/hajimehoshi/ebiten/v2") + out, err := cmd.Output() + if err != nil { + panic(fmt.Sprintf("debug: go list -f {{.Dir}} failed: %v", err)) + } + ebitengineFileDir = filepath.ToSlash(strings.TrimSpace(string(out))) + }) + + // Relying on a caller stacktrace is very fragile, but this is fine as this is only for debugging. + var ebitenPackageReached bool + for i := 0; ; i++ { + _, file, line, ok := runtime.Caller(i) + if !ok { + break + } + // The file should be with a slash, but just in case, convert it. + file = filepath.ToSlash(file) + + if !ebitenPackageReached { + if path.Dir(file) == ebitengineFileDir { + ebitenPackageReached = true + } + continue + } + + if path.Dir(file) == ebitengineFileDir || path.Dir(file) == ebitengineFileDir+"/colorm" || path.Dir(file) == ebitengineFileDir+"/text" || path.Dir(file) == ebitengineFileDir+"/text/v2" { + continue + } + return file, line, true + } + + return "", 0, false +} diff --git a/internal/graphicscommand/commandqueue.go b/internal/graphicscommand/commandqueue.go index 4e01b0cbc..574776d3a 100644 --- a/internal/graphicscommand/commandqueue.go +++ b/internal/graphicscommand/commandqueue.go @@ -18,7 +18,6 @@ import ( "fmt" "image" "math" - "runtime" "strings" "sync" "sync/atomic" @@ -165,20 +164,10 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh c.uniforms = uniforms c.fillRule = fillRule if debug.IsDebug { - // Get the root caller of this function. - // Relying on a caller stacktrace is very fragile, but this is fine as this is only for debugging. - for i := 0; ; i++ { - _, file, _, ok := runtime.Caller(i) - if !ok { - break - } - if !strings.HasSuffix(file, "/ebiten/image.go") { - continue - } - if _, file, line, ok := runtime.Caller(i + 1); ok { - c.firstCaller = fmt.Sprintf("%s:%d", file, line) - } - break + if file, line, ok := debug.FirstCaller(); ok { + c.firstCaller = fmt.Sprintf("%s:%d", file, line) + } else { + c.firstCaller = "(internal)" } } q.commands = append(q.commands, c)