zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

load_windows.go (3981B)


      1 // Copyright 2018 The Ebiten Authors
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package glfw
     16 
     17 import (
     18 	"bytes"
     19 	"compress/gzip"
     20 	"fmt"
     21 	"io"
     22 	"os"
     23 	"path/filepath"
     24 	"unsafe"
     25 
     26 	"golang.org/x/sys/windows"
     27 )
     28 
     29 type dll struct {
     30 	d     *windows.LazyDLL
     31 	procs map[string]*windows.LazyProc
     32 }
     33 
     34 func (d *dll) call(name string, args ...uintptr) uintptr {
     35 	if d.procs == nil {
     36 		d.procs = map[string]*windows.LazyProc{}
     37 	}
     38 	if _, ok := d.procs[name]; !ok {
     39 		d.procs[name] = d.d.NewProc(name)
     40 	}
     41 	// It looks like there is no way to handle Windows errors correctly.
     42 	r, _, _ := d.procs[name].Call(args...)
     43 	return r
     44 }
     45 
     46 func writeDLLFile(name string) error {
     47 	f, err := gzip.NewReader(bytes.NewReader(glfwDLLCompressed))
     48 	if err != nil {
     49 		return err
     50 	}
     51 	defer f.Close()
     52 
     53 	out, err := os.Create(name)
     54 	if err != nil {
     55 		return err
     56 	}
     57 	defer out.Close()
     58 
     59 	if _, err := io.Copy(out, f); err != nil {
     60 		return err
     61 	}
     62 	return nil
     63 }
     64 
     65 func loadDLL() (*dll, error) {
     66 	cachedir, err := os.UserCacheDir()
     67 	if err != nil {
     68 		return nil, err
     69 	}
     70 
     71 	dir := filepath.Join(cachedir, "ebiten")
     72 	if err := os.MkdirAll(dir, 0755); err != nil {
     73 		return nil, err
     74 	}
     75 
     76 	fn := filepath.Join(dir, glfwDLLHash+".dll")
     77 	if _, err := os.Stat(fn); err != nil {
     78 		if !os.IsNotExist(err) {
     79 			return nil, err
     80 		}
     81 
     82 		// Create a DLL as a temporary file and then rename it later.
     83 		// Without the temporary file, writing a DLL might fail in the process of writing and Ebiten cannot
     84 		// notice that the DLL file is incomplete.
     85 		if err := writeDLLFile(fn + ".tmp"); err != nil {
     86 			return nil, err
     87 		}
     88 
     89 		if err := os.Rename(fn+".tmp", fn); err != nil {
     90 			return nil, err
     91 		}
     92 	}
     93 
     94 	return &dll{
     95 		d: windows.NewLazyDLL(fn),
     96 	}, nil
     97 }
     98 
     99 func (d *dll) unload() error {
    100 	if err := windows.FreeLibrary(windows.Handle(d.d.Handle())); err != nil {
    101 		return err
    102 	}
    103 	return nil
    104 }
    105 
    106 func bytePtrToString(ptr *byte) string {
    107 	var bs []byte
    108 	for i := uintptr(0); ; i++ {
    109 		b := *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + i))
    110 		if b == 0 {
    111 			break
    112 		}
    113 		bs = append(bs, b)
    114 	}
    115 	return string(bs)
    116 }
    117 
    118 type glfwError struct {
    119 	code ErrorCode
    120 	desc string
    121 }
    122 
    123 func (e *glfwError) Error() string {
    124 	return fmt.Sprintf("glfw: %s: %s", e.code.String(), e.desc)
    125 }
    126 
    127 var lastErr = make(chan *glfwError, 1)
    128 
    129 func fetchError() *glfwError {
    130 	select {
    131 	case err := <-lastErr:
    132 		return err
    133 	default:
    134 		return nil
    135 	}
    136 }
    137 
    138 func panicError() {
    139 	if err := acceptError(); err != nil {
    140 		panic(err)
    141 	}
    142 }
    143 
    144 func flushErrors() {
    145 	if err := fetchError(); err != nil {
    146 		panic(fmt.Sprintf("glfw: uncaught error: %s", err.Error()))
    147 	}
    148 }
    149 
    150 func acceptError(codes ...ErrorCode) error {
    151 	err := fetchError()
    152 	if err == nil {
    153 		return nil
    154 	}
    155 	for _, c := range codes {
    156 		if err.code == c {
    157 			return err
    158 		}
    159 	}
    160 	switch err.code {
    161 	case PlatformError:
    162 		// TODO: Should we log this?
    163 		return nil
    164 	case NotInitialized, NoCurrentContext, InvalidEnum, InvalidValue, OutOfMemory:
    165 		panic(err)
    166 	default:
    167 		panic(fmt.Sprintf("glfw: uncaught error: %s", err.Error()))
    168 	}
    169 	return err
    170 }
    171 
    172 func goGLFWErrorCallback(code uintptr, desc *byte) uintptr {
    173 	flushErrors()
    174 	err := &glfwError{
    175 		code: ErrorCode(code),
    176 		desc: bytePtrToString(desc),
    177 	}
    178 	select {
    179 	case lastErr <- err:
    180 	default:
    181 		panic(fmt.Sprintf("glfw: uncaught error: %s", err.Error()))
    182 	}
    183 	return 0
    184 }
    185 
    186 var glfwDLL *dll
    187 
    188 func init() {
    189 	dll, err := loadDLL()
    190 	if err != nil {
    191 		panic(err)
    192 	}
    193 	glfwDLL = dll
    194 
    195 	glfwDLL.call("glfwSetErrorCallback", windows.NewCallbackCDecl(goGLFWErrorCallback))
    196 }