A toy MMO example built using Ebiten and WebRTC DataChannels (UDP)

Ebiten WebRTC Toy MMO

⚠️ This is a piece of incomplete hobby work and not robust. Please read the "Why does this project exist?" section.

What is this project?

This is an authoritative server model demonstrating a very simple platformer game where players can move left, right and jump around. When running from the client from the browser, which in my testing, works fine on Chrome, Firefox and on Safari with my iPhone 6S.

screenshot of game running in a browser with the server running natively over the top

Controls:

  • Move Left = Left Arrow Key or Mouse/Touch Left Side of Screen
  • Move Right = Right Arrow Key or Mouse/Touch Right Side of Screen
  • Jump = Spacebar or Mouse/Touch Middle of Screen

Why does this project exist?

This is a project that exists because I wanted practice applying concepts blogged about on Gaffer On Games, get better at networking code and leverage WebRTC to get UDP in the browser, as UDP is recommended over TCP for real-time games.

I consider myself to be game development hobbyist who isn't that experienced, so this project is likely to have code smells, mistakes or whatever else. However, I still thought it'd be valuable to put online, even in it's current incomplete state, because I've personally found that half-baked game developer examples get me closer to solving problems I wanted to solve, even if they lack correctness.

Known problems and design choices

Here's a list of design choices made as well as known problems. There's more I'm probably not thinking of but hopefully they're somewhat commented in the code.

  • We don't timeout the connection of the client reliably. It can hang trying to connect if UDP ports are blocked on either the server or client-side as it'll never end up opening a Data Channel.
  • We don't support ICERestart, ie. if someones connection shifts from WiFi to 4G, the connection will probably be lost.
  • We haven't thought about making the jitter buffer nice for getting client state from the server, so I'm not sure how smooth other players movement will be in poorer network conditions.
  • The server doesn't reflect clients leaving on other clients.
  • If the server is closed, the clients aren't notified or booted out.
  • We chose to create packet data using Go structs and reflection instead of protobuf as protobuf comes with the overhead of requiring additional tools for code generation and adds a non-trivial amount of byte overhead. A Gaffer On Games article goes into detail on why hand-rolling packet types once you know your data is the better option. We didn't end up doing any sort of compression on packet data in this project.

How to run locally and develop

The following commands need to be run from the root directory of the project.

Server

go build -tags "server" -o server && ./server

We also have a headless server option which allows us to run on machines without graphic rendering capabilities.

go build -tags "server headless" -o server && ./server

Client

Native

go build && ./toy-webrtc-mmo

Web

Start a web server that will serve the game client on http://localhost:8080/. This will rebuild the client everytime you refresh the page.

go build -o dev-server ./cmd/dev-server && ./dev-server

How to deploy and server configuration

Client

1.) Build WASM binary

GOOS=js GOARCH=wasm go build -o dist/main.wasm

2.) Zip up contents and put on a host somewhere. I put it on my Amazon EC2 instance and then served it with the "Asset Server" below.

Server

This command builds a headless server which has two advantages:

  • No overhead from rendering or draw calls
  • Cross-compilation with environment variables just works
go build -tags "server headless" -o server

Asset Server (optional)**

This is the server that serves the asset files for the web client:

  • index.html
  • main.wasm
  • wasm_exec.js
  1. Build asset-server from the "cmd/asset-server" folder
go build -o asset-server ./cmd/asset-server
  1. Copy both "asset-server" and the "dist" folder to your server

  2. Run the "asset-server" on your server. For me, my server was an Amazon EC2 instance.

./asset-server

Server configuration

Type Port Description
TCP 50000 Allow HTTP POST access to get WebRTC SDP / ConnectAuth
TCP 8080 (Optional if you serve web files elsewhere) If using Asset Server, allow HTTP access for the web game client (serving assets, WASM file)
UDP 3478 Allow STUN server
UDP 10000 - 11999 UDP ports used by WebRTC DataChannels (We called SetEphemeralUDPPortRange in our code to make the UDP port range predictable / lockdownable)

Credits

  • Pions WebRTC Authors and Contributors for all their hard work on the Golang WebRTC libraries and allowing me to get this working in pure Go.
  • Glenn Fiedler for all their detailed blog posts on doing netcode, including why you dont do certain things.
  • Chen Tao for their tool clumsy, which has been invaluable for simulating and debugging weird network conditions on Windows.
Similar Resources

Peerconnection_explainer - PeerConnection-Explainer parses WebRTC Offers/Answers then provides summaries and suggestions

PeerConnection Explainer PeerConnection Explainer decodes WebRTC... so you don't have too! PeerConnection Explainer parses WebRTC Offers/Answers then

Oct 31, 2022

Pure Go implementation of the WebRTC API

Pure Go implementation of the WebRTC API

Pion WebRTC A pure Go implementation of the WebRTC API New Release Pion WebRTC v3.0.0 has been released! See the release notes to learn about new feat

Jan 1, 2023

Pure Go implementation of the WebRTC API

Pure Go implementation of the WebRTC API

Pure Go implementation of the WebRTC API

Jan 8, 2023

gRPC over WebRTC

gRPC over WebRTC Just a proof of concept, please be kind. How to Start all the things Client, create-react-app + grpc-web signaling + webrtc extension

Dec 16, 2022

Scalable WebRTC Signaling Server with ayame-like protocol.

ayu ayu is WebRTC Signaling Server with ayame-like protocol. Scalable: ayu uses Redis to store room states, so it can be used on serverless platforms

Nov 11, 2022

WebRTC media servers stress testing tool (currently only Janus)

 WebRTC media servers stress testing tool (currently only Janus)

GHODRAT WebRTC media servers stress testing tool (currently only Janus) Architecture Janus media-server under load Deployment ghodrat # update or crea

Nov 9, 2022

This project is the eloboration of pion/webrtc.

This project is the eloboration of pion/webrtc. The idea is to make the (pion/webrtc) sfu-ws example be able to handle multiple rooms

Nov 29, 2021

Go Webrtc Signaling Server

Go Webrtc Signaling Server This package is used to listen for Remote SDP (Sessio

Sep 7, 2022

Overlay networks based on WebRTC.

Overlay networks based on WebRTC.

weron Overlay networks based on WebRTC. ⚠️ weron has not yet been audited! While we try to make weron as secure as possible, it has not yet undergone

Jan 4, 2023
Comments
  • change renderer implementation to make calls inlineable

    change renderer implementation to make calls inlineable

    Why make these changes?

    If you inspect the dumps below, you'll see that after merging, calls like NewImageFromImage end up being inlined. ie.

    .\app.go:37:42: inlining call to "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".(*App).NewImageFromImage method(*"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".App) func(image.Image) rendereriface.Image { return rendereriface.Image("github.com/hajimehoshi/ebiten/v2".NewImageFromImage("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".img)) }
    

    Master branch before merge

    $ go build -gcflags=-m=2
    # github.com/silbinarywolf/toy-webrtc-mmo/internal/app
    .\app_noheadless.go:10:6: can inline getRenderDriver with cost 5 as: func() renderer.App { return new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) }
    .\app.go:29:6: cannot inline (*App).Init: function too complex: cost 354 exceeds budget 80
    .\app.go:30:27: inlining call to getRenderDriver func() renderer.App { return new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) }
    .\app.go:35:48: inlining call to strings.NewReader func(string) *strings.Reader { return &strings.Reader{...} }
    .\app.go:45:57: inlining call to client_or_server.NewClientOrServer func(netconf.Options) netcode.Controller { return netcode.Controller(client.New(client_or_server.options)) }
    .\app.go:110:6: cannot inline GetPlayerInput: unhandled op RANGE
    .\app.go:113:37: inlining call to input.IsKeyPressed func(input.Key) bool { return input.isKeyPressed(input.key) }
    .\app.go:113:37: inlining call to input.isKeyPressed func(input.Key) bool { return "github.com/hajimehoshi/ebiten/v2".IsKeyPressed("github.com/hajimehoshi/ebiten/v2".Key(input.key)) }
    .\app.go:113:71: inlining call to input.IsKeyPressed func(input.Key) bool { return input.isKeyPressed(input.key) }
    .\app.go:113:71: inlining call to input.isKeyPressed func(input.Key) bool { return "github.com/hajimehoshi/ebiten/v2".IsKeyPressed("github.com/hajimehoshi/ebiten/v2".Key(input.key)) }
    .\app.go:114:37: inlining call to input.IsKeyPressed func(input.Key) bool { return input.isKeyPressed(input.key) }
    .\app.go:114:37: inlining call to input.isKeyPressed func(input.Key) bool { return "github.com/hajimehoshi/ebiten/v2".IsKeyPressed("github.com/hajimehoshi/ebiten/v2".Key(input.key)) }
    .\app.go:114:71: inlining call to input.IsKeyPressed func(input.Key) bool { return input.isKeyPressed(input.key) }
    .\app.go:114:71: inlining call to input.isKeyPressed func(input.Key) bool { return "github.com/hajimehoshi/ebiten/v2".IsKeyPressed("github.com/hajimehoshi/ebiten/v2".Key(input.key)) }
    .\app.go:115:37: inlining call to input.IsKeyPressed func(input.Key) bool { return input.isKeyPressed(input.key) }
    .\app.go:115:37: inlining call to input.isKeyPressed func(input.Key) bool { return "github.com/hajimehoshi/ebiten/v2".IsKeyPressed("github.com/hajimehoshi/ebiten/v2".Key(input.key)) }
    .\app.go:124:32: inlining call to input.IsMouseButtonPressed func(input.MouseButton) bool { return input.isMouseButtonPressed(input.mouseButton) }
    .\app.go:124:32: inlining call to input.isMouseButtonPressed func(input.MouseButton) bool { return "github.com/hajimehoshi/ebiten/v2".IsMouseButtonPressed("github.com/hajimehoshi/ebiten/v2".MouseButton(input.mouseButton)) }
    .\app.go:142:41: inlining call to input.TouchIDs func() []input.TouchID { return input.touchIDs() }
    .\app.go:53:6: cannot inline (*App).Update: unhandled op RANGE
    .\app.go:89:6: cannot inline (*App).Draw: unhandled op RANGE
    .\app.go:102:14: inlining call to ent.(*Player).Draw method(*ent.Player) func(renderer.Screen) { var ent.s·3 renderer.Image; ent.s·3 = <N>; ent.s·3 = ent.rightSprite; if ent.self.DirLeft { ent.s·3 = ent.leftSprite }; ent.screen.DrawImage(ent.s·3, renderer.ImageOptions{...}) }
    .\app.go:166:6: can inline (*App).Layout with cost 3 as: method(*App) func(int, int) (int, int) { return world.ScreenWidth, world.ScreenHeight }
    .\app.go:170:6: cannot inline StartApp: function too complex: cost 208 exceeds budget 80
    .\app.go:171:24: inlining call to getRenderDriver func() renderer.App { return new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) }
    .\app.go:172:19: devirtualizing drv.SetWindowSize to *"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App
    .\app.go:173:20: devirtualizing drv.SetWindowTitle to *"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App
    .\app.go:174:23: devirtualizing drv.RunGame to *"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App
    .\app.go:35:48: &strings.Reader{...} escapes to heap:
    .\app.go:35:48:   flow: ~R0 = &{storage for &strings.Reader{...}}:
    .\app.go:35:48:     from &strings.Reader{...} (spill) at .\app.go:35:48
    .\app.go:35:48:     from ~R0 = <N> (assign-pair) at .\app.go:35:48
    .\app.go:35:48:   flow: {heap} = ~R0:
    .\app.go:35:48:     from strings.NewReader(asset.Background) (interface-converted) at .\app.go:35:48
    .\app.go:35:48:     from image.Decode(strings.NewReader(asset.Background)) (call parameter) at .\app.go:35:30
    .\app.go:29:7: parameter app leaks to {heap} with derefs=0:
    .\app.go:29:7:   flow: {heap} = app:
    .\app.go:29:7:     from app (interface-converted) at .\app.go:34:23
    .\app.go:29:7:     from ent.LoadPlayerAssets(app) (call parameter) at .\app.go:34:23
    .\app.go:30:27: new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) escapes to heap:
    .\app.go:30:27:   flow: ~R0 = &{storage for new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App)}:
    .\app.go:30:27:     from new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) (spill) at .\app.go:30:27
    .\app.go:30:27:     from new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) (interface-converted) at .\app.go:30:27
    .\app.go:30:27:     from ~R0 = <N> (assign-pair) at .\app.go:30:27
    .\app.go:30:27:   flow: {heap} = ~R0:
    .\app.go:30:27:     from app.App = renderer.App(~R0) (assign) at .\app.go:30:10
    .\app.go:29:7: leaking param: app
    .\app.go:30:27: new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) escapes to heap
    .\app.go:35:48: &strings.Reader{...} escapes to heap
    .\app.go:53:7: parameter app leaks to {heap} with derefs=0:
    .\app.go:53:7:   flow: {heap} = app:
    .\app.go:53:7:     from app.Init() (call parameter) at .\app.go:55:11
    .\app.go:53:7: leaking param: app
    .\app.go:89:22: parameter screen leaks to {heap} with derefs=0:
    .\app.go:89:22:   flow: {heap} = screen:
    .\app.go:89:22:     from screen.DrawImage(backgroundImage, renderer.ImageOptions{...}) (call parameter) at .\app.go:95:18
    .\app.go:89:7: parameter app leaks to {heap} with derefs=1:
    .\app.go:89:7:   flow: {heap} = *app:
    .\app.go:89:7:     from app.clientOrServer (dot of pointer) at .\app.go:90:9
    .\app.go:89:7:     from app.clientOrServer.HasStartedOrConnected() (call parameter) at .\app.go:90:46
    .\app.go:89:7: leaking param content: app
    .\app.go:89:22: leaking param: screen
    .\app.go:166:7: g does not escape
    .\app.go:174:24: &App{} escapes to heap:
    .\app.go:174:24:   flow: {heap} = &{storage for &App{}}:
    .\app.go:174:24:     from &App{} (spill) at .\app.go:174:24
    .\app.go:174:24:     from &App{} (interface-converted) at .\app.go:174:24
    .\app.go:174:24:     from drv.(*"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App).RunGame(&App{}) (call parameter) at .\app.go:174:23
    .\app.go:171:24: new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) does not escape
    .\app.go:174:24: &App{} escapes to heap
    .\app_noheadless.go:11:12: new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) escapes to heap:
    .\app_noheadless.go:11:12:   flow: ~r0 = &{storage for new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App)}:
    .\app_noheadless.go:11:12:     from new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) (spill) at .\app_noheadless.go:11:12
    .\app_noheadless.go:11:12:     from new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) (interface-converted) at .\app_noheadless.go:11:12
    .\app_noheadless.go:11:12:     from return new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) (return) at .\app_noheadless.go:11:2
    .\app_noheadless.go:11:12: new("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/ebiten".App) escapes to heap
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:25:20: parameter renderer.img leaks to {heap} with derefs=0:
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:25:20:   flow: {heap} = renderer.img:
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:25:20:     from .this.App.NewImageFromImage(renderer.img) (call parameter) at <autogenerated>:1
    <autogenerated>:1: parameter .this leaks to {heap} with derefs=1:
    <autogenerated>:1:   flow: {heap} = *.this:
    <autogenerated>:1:     from .this.App (dot of pointer) at <autogenerated>:1
    <autogenerated>:1:     from .this.App.NewImageFromImage(renderer.img) (call parameter) at <autogenerated>:1
    <autogenerated>:1: leaking param content: .this
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:25:20: leaking param: img
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:24:10: parameter renderer.game leaks to {heap} with derefs=0:
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:24:10:   flow: {heap} = renderer.game:
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:24:10:     from .this.App.RunGame(renderer.game) (call parameter) at <autogenerated>:1
    <autogenerated>:1: parameter .this leaks to {heap} with derefs=1:
    <autogenerated>:1:   flow: {heap} = *.this:
    <autogenerated>:1:     from .this.App (dot of pointer) at <autogenerated>:1
    <autogenerated>:1:     from .this.App.RunGame(renderer.game) (call parameter) at <autogenerated>:1
    <autogenerated>:1: leaking param content: .this
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:24:10: leaking param: game
    <autogenerated>:1: parameter .this leaks to {heap} with derefs=1:
    <autogenerated>:1:   flow: {heap} = *.this:
    <autogenerated>:1:     from .this.App (dot of pointer) at <autogenerated>:1
    <autogenerated>:1:     from .this.App.SetRunnableOnUnfocused(renderer.v) (call parameter) at <autogenerated>:1
    <autogenerated>:1: leaking param content: .this
    <autogenerated>:1: parameter .this leaks to {heap} with derefs=1:
    <autogenerated>:1:   flow: {heap} = *.this:
    <autogenerated>:1:     from .this.App (dot of pointer) at <autogenerated>:1
    <autogenerated>:1:     from .this.App.SetWindowSize(renderer.screenWidth, renderer.screenHeight) (call parameter) at <autogenerated>:1
    <autogenerated>:1: leaking param content: .this
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:23:17: parameter renderer.title leaks to {heap} with derefs=0:
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:23:17:   flow: {heap} = renderer.title:
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:23:17:     from .this.App.SetWindowTitle(renderer.title) (call parameter) at <autogenerated>:1
    <autogenerated>:1: parameter .this leaks to {heap} with derefs=1:
    <autogenerated>:1:   flow: {heap} = *.this:
    <autogenerated>:1:     from .this.App (dot of pointer) at <autogenerated>:1
    <autogenerated>:1:     from .this.App.SetWindowTitle(renderer.title) (call parameter) at <autogenerated>:1
    <autogenerated>:1: leaking param content: .this
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:23:17: leaking param: title
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:25:20: parameter renderer.img leaks to {heap} with derefs=0:
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:25:20:   flow: {heap} = renderer.img:
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:25:20:     from .this.App.NewImageFromImage(renderer.img) (call parameter) at <autogenerated>:1
    <autogenerated>:1: parameter .this leaks to {heap} with derefs=0:
    <autogenerated>:1:   flow: {heap} = .this:
    <autogenerated>:1:     from .this.App (dot) at <autogenerated>:1
    <autogenerated>:1:     from .this.App.NewImageFromImage(renderer.img) (call parameter) at <autogenerated>:1
    <autogenerated>:1: leaking param: .this
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:25:20: leaking param: img
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:24:10: parameter renderer.game leaks to {heap} with derefs=0:
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:24:10:   flow: {heap} = renderer.game:
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:24:10:     from .this.App.RunGame(renderer.game) (call parameter) at <autogenerated>:1
    <autogenerated>:1: parameter .this leaks to {heap} with derefs=0:
    <autogenerated>:1:   flow: {heap} = .this:
    <autogenerated>:1:     from .this.App (dot) at <autogenerated>:1
    <autogenerated>:1:     from .this.App.RunGame(renderer.game) (call parameter) at <autogenerated>:1
    <autogenerated>:1: leaking param: .this
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:24:10: leaking param: game
    <autogenerated>:1: parameter .this leaks to {heap} with derefs=0:
    <autogenerated>:1:   flow: {heap} = .this:
    <autogenerated>:1:     from .this.App (dot) at <autogenerated>:1
    <autogenerated>:1:     from .this.App.SetRunnableOnUnfocused(renderer.v) (call parameter) at <autogenerated>:1
    <autogenerated>:1: leaking param: .this
    <autogenerated>:1: parameter .this leaks to {heap} with derefs=0:
    <autogenerated>:1:   flow: {heap} = .this:
    <autogenerated>:1:     from .this.App (dot) at <autogenerated>:1
    <autogenerated>:1:     from .this.App.SetWindowSize(renderer.screenWidth, renderer.screenHeight) (call parameter) at <autogenerated>:1
    <autogenerated>:1: leaking param: .this
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:23:17: parameter renderer.title leaks to {heap} with derefs=0:
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:23:17:   flow: {heap} = renderer.title:
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:23:17:     from .this.App.SetWindowTitle(renderer.title) (call parameter) at <autogenerated>:1
    <autogenerated>:1: parameter .this leaks to {heap} with derefs=0:
    <autogenerated>:1:   flow: {heap} = .this:
    <autogenerated>:1:     from .this.App (dot) at <autogenerated>:1
    <autogenerated>:1:     from .this.App.SetWindowTitle(renderer.title) (call parameter) at <autogenerated>:1
    <autogenerated>:1: leaking param: .this
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\renderer\renderer.go:23:17: leaking param: title
    

    Master branch after merge

    # github.com/silbinarywolf/toy-webrtc-mmo/internal/app
    .\app.go:29:6: cannot inline (*App).Init: function too complex: cost 365 exceeds budget 80
    .\app.go:33:48: inlining call to strings.NewReader func(string) *strings.Reader { return &strings.Reader{...} }
    .\app.go:37:42: inlining call to "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".(*App).NewImageFromImage method(*"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".App) func(image.Image) rendereriface.Image { return rendereriface.Image("github.com/hajimehoshi/ebiten/v2".NewImageFromImage("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".img)) }
    .\app.go:40:28: inlining call to "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".(*App).SetRunnableOnUnfocused method(*"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".App) func(bool) { "github.com/hajimehoshi/ebiten/v2".SetRunnableOnUnfocused("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".v) }
    .\app.go:40:28: inlining call to "github.com/hajimehoshi/ebiten/v2".SetRunnableOnUnfocused func(bool) { "github.com/hajimehoshi/ebiten/v2".uiDriver().SetRunnableOnUnfocused("github.com/hajimehoshi/ebiten/v2".runnableOnUnfocused) }
    .\app.go:40:28: inlining call to "github.com/hajimehoshi/ebiten/v2".uiDriver func() driver.UI { return driver.UI("github.com/hajimehoshi/ebiten/v2/internal/uidriver/glfw".Get()) }
    .\app.go:40:28: inlining call to "github.com/hajimehoshi/ebiten/v2/internal/uidriver/glfw".Get func() *"github.com/hajimehoshi/ebiten/v2/internal/uidriver/glfw".UserInterface { return "github.com/hajimehoshi/ebiten/v2/internal/uidriver/glfw".theUI }
    .\app.go:43:57: inlining call to client_or_server.NewClientOrServer func(netconf.Options) netcode.Controller { return netcode.Controller(client.New(client_or_server.options)) }
    .\app.go:108:6: cannot inline GetPlayerInput: unhandled op RANGE
    .\app.go:111:37: inlining call to input.IsKeyPressed func(input.Key) bool { return input.isKeyPressed(input.key) }
    .\app.go:111:37: inlining call to input.isKeyPressed func(input.Key) bool { return "github.com/hajimehoshi/ebiten/v2".IsKeyPressed("github.com/hajimehoshi/ebiten/v2".Key(input.key)) }
    .\app.go:111:71: inlining call to input.IsKeyPressed func(input.Key) bool { return input.isKeyPressed(input.key) }
    .\app.go:111:71: inlining call to input.isKeyPressed func(input.Key) bool { return "github.com/hajimehoshi/ebiten/v2".IsKeyPressed("github.com/hajimehoshi/ebiten/v2".Key(input.key)) }
    .\app.go:112:37: inlining call to input.IsKeyPressed func(input.Key) bool { return input.isKeyPressed(input.key) }
    .\app.go:112:37: inlining call to input.isKeyPressed func(input.Key) bool { return "github.com/hajimehoshi/ebiten/v2".IsKeyPressed("github.com/hajimehoshi/ebiten/v2".Key(input.key)) }
    .\app.go:112:71: inlining call to input.IsKeyPressed func(input.Key) bool { return input.isKeyPressed(input.key) }
    .\app.go:112:71: inlining call to input.isKeyPressed func(input.Key) bool { return "github.com/hajimehoshi/ebiten/v2".IsKeyPressed("github.com/hajimehoshi/ebiten/v2".Key(input.key)) }
    .\app.go:113:37: inlining call to input.IsKeyPressed func(input.Key) bool { return input.isKeyPressed(input.key) }
    .\app.go:113:37: inlining call to input.isKeyPressed func(input.Key) bool { return "github.com/hajimehoshi/ebiten/v2".IsKeyPressed("github.com/hajimehoshi/ebiten/v2".Key(input.key)) }
    .\app.go:122:32: inlining call to input.IsMouseButtonPressed func(input.MouseButton) bool { return input.isMouseButtonPressed(input.mouseButton) }
    .\app.go:122:32: inlining call to input.isMouseButtonPressed func(input.MouseButton) bool { return "github.com/hajimehoshi/ebiten/v2".IsMouseButtonPressed("github.com/hajimehoshi/ebiten/v2".MouseButton(input.mouseButton)) }
    .\app.go:140:41: inlining call to input.TouchIDs func() []input.TouchID { return input.touchIDs() }
    .\app.go:51:6: cannot inline (*App).Update: unhandled op RANGE
    .\app.go:87:6: cannot inline (*App).Draw: unhandled op RANGE
    .\app.go:100:14: inlining call to ent.(*Player).Draw method(*ent.Player) func(rendereriface.Screen) { var ent.s·3 rendereriface.Image; ent.s·3 = <N>; ent.s·3 = ent.rightSprite; if ent.self.DirLeft { ent.s·3 = ent.leftSprite }; ent.screen.DrawImage(ent.s·3, rendereriface.ImageOptions{...}) }
    .\app.go:164:6: can inline (*App).Layout with cost 3 as: method(*App) func(int, int) (int, int) { return world.ScreenWidth, world.ScreenHeight }
    .\app.go:168:6: cannot inline StartApp: function too complex: cost 230 exceeds budget 80
    .\app.go:170:19: inlining call to "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".(*App).SetWindowSize method(*"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".App) func(int, int) { "github.com/hajimehoshi/ebiten/v2".SetWindowSize("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".width, "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".height) }
    .\app.go:171:20: inlining call to "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".(*App).SetWindowTitle method(*"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".App) func(string) { "github.com/hajimehoshi/ebiten/v2".SetWindowTitle("github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".title) }
    .\app.go:172:27: inlining call to "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".(*App).RunGame method(*"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".App) func(rendereriface.Game) error { var "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4 "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".ebitenGameAndScreen; "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4 = <N>; "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4 = "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".ebitenGameAndScreen{}; "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4.Game = "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".game; return "github.com/hajimehoshi/ebiten/v2".RunGame("github.com/hajimehoshi/ebiten/v2".Game(&"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4)) }
    .\app.go:40:28: devirtualizing driver.UI(~R0).SetRunnableOnUnfocused to *"github.com/hajimehoshi/ebiten/v2/internal/uidriver/glfw".UserInterface
    .\app.go:33:48: &strings.Reader{...} escapes to heap:
    .\app.go:33:48:   flow: ~R0 = &{storage for &strings.Reader{...}}:
    .\app.go:33:48:     from &strings.Reader{...} (spill) at .\app.go:33:48
    .\app.go:33:48:     from ~R0 = <N> (assign-pair) at .\app.go:33:48
    .\app.go:33:48:   flow: {heap} = ~R0:
    .\app.go:33:48:     from strings.NewReader(asset.Background) (interface-converted) at .\app.go:33:48
    .\app.go:33:48:     from image.Decode(strings.NewReader(asset.Background)) (call parameter) at .\app.go:33:30
    .\app.go:29:7: app does not escape
    .\app.go:33:48: &strings.Reader{...} escapes to heap
    .\app.go:51:7: parameter app leaks to {heap} with derefs=0:
    .\app.go:51:7:   flow: {heap} = app:
    .\app.go:51:7:     from app.world (dot of pointer) at .\app.go:73:38
    .\app.go:51:7:     from &app.world (address-of) at .\app.go:73:34
    .\app.go:51:7:     from app.clientOrServer.BeforeUpdate(&app.world) (call parameter) at .\app.go:73:33
    .\app.go:51:7: parameter app leaks to {heap} with derefs=0:
    .\app.go:51:7:   flow: {heap} = app:
    .\app.go:51:7:     from app.world (dot of pointer) at .\app.go:73:38
    .\app.go:51:7:     from &app.world (address-of) at .\app.go:73:34
    .\app.go:51:7:     from app.clientOrServer.BeforeUpdate(&app.world) (call parameter) at .\app.go:73:33
    .\app.go:51:7: leaking param: app
    .\app.go:87:22: parameter screen leaks to {heap} with derefs=0:
    .\app.go:87:22:   flow: {heap} = screen:
    .\app.go:87:22:     from screen.DrawImage(backgroundImage, rendereriface.ImageOptions{...}) (call parameter) at .\app.go:93:18
    .\app.go:87:7: parameter app leaks to {heap} with derefs=1:
    .\app.go:87:7:   flow: {heap} = *app:
    .\app.go:87:7:     from app.clientOrServer (dot of pointer) at .\app.go:88:9
    .\app.go:87:7:     from app.clientOrServer.HasStartedOrConnected() (call parameter) at .\app.go:88:46
    .\app.go:87:7: leaking param content: app
    .\app.go:87:22: leaking param: screen
    .\app.go:164:7: g does not escape
    .\app.go:172:27: "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4 escapes to heap:
    .\app.go:172:27:   flow: {heap} = &"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4:
    .\app.go:172:27:     from &"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4 (address-of) at .\app.go:172:27
    .\app.go:172:27:     from "github.com/hajimehoshi/ebiten/v2".Game(&"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4) (interface-converted) at .\app.go:172:27
    .\app.go:172:27:     from "github.com/hajimehoshi/ebiten/v2".RunGame("github.com/hajimehoshi/ebiten/v2".Game(&"github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4)) (call parameter) at .\app.go:172:27
    .\app.go:169:9: &App{} escapes to heap:
    .\app.go:169:9:   flow: app = &{storage for &App{}}:
    .\app.go:169:9:     from &App{} (spill) at .\app.go:169:9
    .\app.go:169:9:     from app := &App{} (assign) at .\app.go:169:6
    .\app.go:169:9:   flow: "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".game = app:
    .\app.go:169:9:     from app (interface-converted) at .\app.go:172:27
    .\app.go:169:9:     from "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".app, "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".game := app.App, app (assign-pair) at .\app.go:172:27
    .\app.go:169:9:   flow: "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4 = "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".game:
    .\app.go:169:9:     from "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4.Game = "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".game (assign) at .\app.go:172:27
    .\app.go:172:27: moved to heap: "github.com/silbinarywolf/toy-webrtc-mmo/internal/renderer/internal/ebiten".gameWrapper·4
    .\app.go:169:9: &App{} escapes to heap
    <autogenerated>:1: .this does not escape
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\internal\ebiten\ebiten.go:37:35: img does not escape
    <autogenerated>:1: .this does not escape
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\internal\ebiten\ebiten.go:41:25: game does not escape
    <autogenerated>:1: .this does not escape
    <autogenerated>:1: .this does not escape
    <autogenerated>:1: .this does not escape
    D:\GoProjects\toy-webrtc-mmo-public\internal\renderer\internal\ebiten\ebiten.go:33:32: title does not escape
    <autogenerated>:1: parameter .this leaks to {heap} with derefs=1:
    <autogenerated>:1:   flow: {heap} = *.this:
    <autogenerated>:1:     from .this.Game (dot of pointer) at <autogenerated>:1
    <autogenerated>:1:     from .this.Game.Layout(rendereriface.outsideWidth, rendereriface.outsideHeight) (call parameter) at <autogenerated>:1
    <autogenerated>:1: leaking param content: .this
    <autogenerated>:1: parameter .this leaks to {heap} with derefs=1:
    <autogenerated>:1:   flow: {heap} = *.this:
    <autogenerated>:1:     from .this.Game (dot of pointer) at <autogenerated>:1
    <autogenerated>:1:     from .this.Game.Update() (call parameter) at <autogenerated>:1
    
UDP Transport: compress, encrypt and send any data reliably over unreliable UDP connections

udpt UDP Transport Compresses, encrypts and transfers data between a sender and receiver using UDP protocol. Features and Design Aims: Avoid the overh

Nov 5, 2022
UDP output for beats to send events over UDP.

beats-udp-output How To Use Clone this project to elastic/beats/libbeat/output/ Modify elastic/beats/libbeat/publisher/includes/includes.go : // add i

Dec 11, 2021
Example of using Pion WebRTC to play H264 + Ogg from disk

This repo demonstrates how you can use Pion WebRTC to play H264 and Ogg from disk. These same APIs can be used to pull from other sources. You can use

Sep 18, 2021
gopunch is a go implementation of a peer-to-peer chat service built using UDP hole punching.

Gopunch gopunch is a go implementation of a peer-to-peer chat service built using UDP hole punching. This is a toy implementation that I put together

May 24, 2022
A simple toy example for running Graphsync + libp2p.
A simple toy example for running Graphsync + libp2p.

graphsync-example Here we outline a simple toy example (main.go) where two local peers transfer Interplanetary Linked Data (IPLD) graphs using Graphsy

Dec 8, 2021
Demonstration of using Pion WebRTC with a shared socket

pion-webrtc-shared-socket This example demonstrates how Pion WebRTC can use an already listening UDP socket. On startup we listen on UDP Socket 8000.

Apr 4, 2022
Pure-Go library for cross-platform local peer discovery using UDP multicast :woman: :repeat: :woman:
Pure-Go library for cross-platform local peer discovery using UDP multicast :woman: :repeat: :woman:

peerdiscovery Pure-go library for cross-platform thread-safe local peer discovery using UDP multicast. I needed to use peer discovery for croc and eve

Jan 8, 2023
P2P Forwarder - a tool for farwarding tcp/udp ports. Made using libp2p.
P2P Forwarder - a tool for farwarding tcp/udp ports. Made using libp2p.

P2P Forwarder A tool for farwarding ports. Made using libp2p. How it works A: opens desired ports ports inside P2P Forwarder A: shares it's id from P2

Nov 14, 2022
A yet to be voice call application in terminal. with the power of go and webRTC (pion).

Kenny I'm just trying to make a cli operated voice call chat application using go with help of webRTC and PortAudio. It might stay a Work In Progress

Dec 2, 2022