gg/engine.go

513 lines
9.6 KiB
Go

package gg
import (
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"vultras.su/core/gods/maps"
//"fmt"
"time"
"slices"
"sync"
)
const (
MaxVertices = 1 << 16
)
type GraphicsLibrary = ebiten.GraphicsLibrary
type RunOptions = ebiten.RunGameOptions
// The type represents order of drawing.
// Higher values are drawn later.
type Layer float64
func (l Layer) GetLayer() Layer {
return l
}
// Window configuration type.
type WindowConfig struct {
DebugInfo ebiten.DebugInfo
Options *RunOptions
// The title of the window.
Title string
// Width and height of the window
// in pixels.
Width,
Height int
// Optional settings with
// self describing names.
FixedSize,
Fullscreen,
VSync bool
}
type Objects struct {
store []Objecter
}
// The main structure that represents current state of [game] engine.
type Engine struct {
wcfg *WindowConfig
// The main holder for objects.
// Uses the map structure to quickly
// delete and create new objects.
Objects *Objects
// The main camera to display in window.
// If is set to nil then the engine will panic.
Camera *Camera
drawLastTime time.Time
drawdt Duration
// Frame delta time.
dt Duration
lastTime time.Time
// Temporary stuff
keys, prevKeys []Key
buttons MouseButtonMap
wheel Vector
cursorPos Vector
outerEvents, handleEvents EventChan
wg sync.WaitGroup
//bufs [LayerBufSize]*Image
vertices map[Layer] []ebiten.Vertex
//vindices []uint16
// Draw frame.
dframe uint
// Frame.
frame uint
runes []rune
}
type engine Engine
// Get currently pressed keys.
func (e *Engine) Keys() []Key {
return e.keys
}
func (e *Engine) GraphicsLibrary() GraphicsLibrary {
return e.wcfg.DebugInfo.GraphicsLibrary
}
// Returns currently pressed buttons.
func (e *Engine) MouseButtons() []MouseButton {
ret := make([]MouseButton, len(e.buttons))
i := 0
for v := range e.buttons {
ret[i] = v
i++
}
slices.Sort(ret)
return ret
}
// Returns new empty Engine.
func NewEngine(
cfg *WindowConfig,
) *Engine {
/*w := Float(cfg.Width)
h := Float(cfg.Height)*/
ret := &Engine{}
ret.wcfg = cfg
ret.Camera = ret.NewCamera()
ret.outerEvents = make(EventChan)
ret.handleEvents = make(EventChan)
ret.Objects = &Objects{}
ret.buttons = MouseButtonMap{}
return ret
}
// Get the real window size in the current context.
func (c *Engine) RealWinSize() Vector {
var w, h int
if c.wcfg.Fullscreen {
w, h = ebiten.ScreenSizeInFullscreen()
} else {
w, h = c.wcfg.Width, c.wcfg.Height
}
return V(
Float(w),
Float(h),
)
}
func (c *Engine) AbsWinSize() Vector {
return c.RealWinSize().Div(c.Camera.Scale)
}
func (e *Engine) EventInput() EventChan {
return e.outerEvents
}
// Add new object considering what
// interfaces it implements.
func (e *Engine) Spawn(b Objecter) error {
/*if e.Objects.Has(b) {
return ObjectExistErr
}*/
b.Start(&Context{Engine: e})
obj := b.GetObject()
obj.input = make(chan *Context)
go func() {
for c := range obj.input {
switch c.typ{
case updateContext :
b.Update(c)
case resolveContext :
b.Resolve(c)
}
e.wg.Done()
}
}()
e.Objects.store = append(e.Objects.store, b)
return nil
}
// Delete object from Engine.
func (e *Engine) Del(b Objecter) error {
/*if !e.Objects.Has(b) {
return ObjectNotExistErr
}
b.Delete(&Context{Engine: e})
e.Objects.Del(b)*/
return nil
}
var (
allButtons = []MouseButton{
MouseButton0,
MouseButton1,
MouseButton2,
MouseButton3,
MouseButton4,
}
)
func (e *Engine) IsPressed(k Key) bool {
keys := e.Keys()
for _, v := range keys {
if v == k {
return true
}
}
return false
}
func (e *Engine) IsButtoned(b MouseButton) bool {
_, ok := e.buttons[b]
return ok
}
func (e *Engine) Wheel() Vector {
return e.wheel
}
func (e *Engine) cursorPosition() Vector {
x, y := ebiten.CursorPosition()
return V(Float(x), Float(y))
}
func (e *Engine) CursorPosition() Vector {
return e.cursorPos
}
func (e *Engine) AbsCursorPosition() Vector {
return e.CursorPosition().Apply(e.Camera.AbsMatrix())
}
func (e *Engine) Runes() []rune {
return e.runes
}
func (e *engine) UpdateEvents() []any {
eng := (*Engine)(e)
e.prevKeys = e.keys
e.keys = inpututil.
AppendPressedKeys(e.keys[:0])
events := []any{}
btns := e.buttons
for _, btn := range allButtons {
if inpututil.IsMouseButtonJustPressed(btn) {
btns[btn] = struct{}{}
events = append(events, &MouseButtonDown{
MouseButton: btn,
})
} else if inpututil.IsMouseButtonJustReleased(btn) {
delete(btns, btn)
events = append(events, &MouseButtonUp{
MouseButton: btn,
})
}
}
x, y := ebiten.Wheel()
e.wheel = V(x, y)
if !(e.wheel.Eq(ZV)) {
events = append(events, &WheelChange{
Offset: e.wheel,
})
}
keyDiff := diffEm(e.prevKeys, e.keys)
for _, key := range keyDiff {
var event any
if eng.IsPressed(key) {
event = &KeyDown{
Key: key,
}
} else {
event = &KeyUp{
Key: key,
}
}
events = append(events, event)
}
realPos := eng.cursorPosition()
if !realPos.Eq(e.cursorPos) {
absM := eng.Camera.AbsMatrix()
absPrevPos := e.cursorPos.Apply(absM)
absPos := realPos.Apply(absM)
events = append(events, &MouseMove{
Real: realPos.Sub(e.cursorPos),
Abs: absPos.Sub(absPrevPos),
})
e.cursorPos = realPos
}
return events
}
func (e *engine) Update() error {
eng := (*Engine)(e)
e.dt = time.Since(e.lastTime)
e.runes = ebiten.AppendInputChars(e.runes[:0])
//fmt.Println("runes:", e.runes)
// Buffering the context for faster.
// Providing the events to the objects.
// Maybe should think of the better way,
// but for it is simple enough.
events := e.UpdateEvents()
c := &Context{
Engine: eng,
typ: updateContext,
Events: events,
}
for _, object := range e.Objects.store {
e.wg.Add(1)
object.Input() <- c
}
e.wg.Wait()
e.Resolve()
e.lastTime = time.Now()
e.frame++
return nil
}
func (e *engine) Resolve() {
eng := (*Engine)(e)
type resHold struct{
Resolver Resolver
input chan *Context
}
colliders := map[CollisionType] []Collider{}
resolvers := []resHold{}
for _, object := range e.Objects.store {
if object.IsResolvable() {
interests := object.GetCollisionInterest()
for _, interest := range interests {
_, ok := colliders[interest]
if !ok {
colliders[interest] = []Collider{}
}
}
resolvers = append(resolvers, resHold{
object,
object.Input(),
})
}
}
for _, object := range e.Objects.store {
if object.IsCollidable() {
typ := object.CollisionType()
_, ok := colliders[typ]
if !ok {
continue
}
colliders[typ] = append(colliders[typ], object)
}
}
for _, resolver := range resolvers {
interests := resolver.Resolver.GetCollisionInterest()
cols := []Collision{}
for _, interest := range interests {
cols = append(cols, GetCollisions(resolver.Resolver, colliders[interest])...)
}
if len(cols) > 0 {
c := &Context{
typ: resolveContext,
Collisions: cols,
Engine: eng,
}
e.wg.Add(1)
resolver.input <- c
}
}
e.wg.Wait()
}
var (
fullPageIndexes = func() [MaxVertices]uint16 {
ret := [MaxVertices]uint16{}
for i:=0 ; i<len(ret) ; i++ {
ret[i] = uint16(i)
}
return ret
}()
defaultPageImg = func() *Image {
img := NewImage(1, 1)
img.Set(0, 0, Rgba(1, 1, 1, 1))
return img
}()
defaultTriOptions = &ebiten.DrawTrianglesOptions{}
)
func (e *engine) Draw(img *ebiten.Image) {
e.drawdt = time.Since(e.drawLastTime)
eng := (*Engine)(e)
m := map[Layer][]Drawer{}
for _, object := range eng.Objects.store {
// Skipping the ones we do not need to draw.
if !object.IsVisible() {
continue
}
l := object.GetLayer()
layer, ok := m[l]
// Create new if has no the layer
if !ok {
m[l] = []Drawer{object}
continue
}
m[l] = append(layer, object)
}
// Drawing layers via the sparse array.
// First drawing via the inside function
// and then the returned []EVertex.
layers := maps.NewSparse[Layer, []Drawer](nil, m)
c := &Context{Engine: eng, typ: drawContext, Image: img}
for layer := range layers.Chan() {
vertices := []EVertex{}
for _, drawer := range layer {
vs := drawer.Draw(c)
if vs != nil {
vertices = append(vertices, vs...)
}
}
pn := len(vertices) / MaxVertices
mod := len(vertices) % MaxVertices
for i := 0 ; i<pn ; i++ {
cur := i*MaxVertices
img.DrawTriangles(
vertices[cur:cur+MaxVertices],
fullPageIndexes[:],
defaultPageImg,
defaultTriOptions,
)
}
st := pn*MaxVertices
img.DrawTriangles(
vertices[st:],
fullPageIndexes[:mod],
defaultPageImg,
defaultTriOptions,
)
}
// Empty the buff to generate it again.
eng.Camera.buffered = false
e.drawLastTime = time.Now()
e.dframe++
}
func (e *engine) Layout(ow, oh int) (int, int) {
if e.wcfg.FixedSize {
return e.wcfg.Width, e.wcfg.Height
}
return ow, oh
}
// Return the delta time.
func (e *Engine) DrawDt() Duration {
return e.drawdt
}
func (e *Engine) Dframe() uint {
return e.dframe
}
// Return the current fixed delta time.
func (e *Engine) RealDt() Duration {
return e.dt
}
func (e *Engine) Dt() Duration {
return time.Second/60
}
func (e *Engine) Frame() uint {
return e.frame
}
func (e *Engine) Fps() float64 {
return ebiten.ActualFPS()
}
func (e *Engine) Tps() float64 {
return ebiten.ActualTPS()
}
func (e *Engine) Run() error {
ebiten.ReadDebugInfo(&e.wcfg.DebugInfo)
ebiten.SetWindowTitle(e.wcfg.Title)
ebiten.SetWindowSize(e.wcfg.Width, e.wcfg.Height)
ebiten.SetWindowSizeLimits(1, 1, e.wcfg.Width, e.wcfg.Height)
ebiten.SetFullscreen(e.wcfg.Fullscreen)
ebiten.SetVsyncEnabled(e.wcfg.VSync)
e.lastTime = time.Now()
//fmt.Println(e.Objects)
return ebiten.RunGameWithOptions((*engine)(e), e.wcfg.Options)
}