Remove unnecessary clientID, fix imageSmoothing parameter reset

This commit is contained in:
Kioubit 2024-07-17 19:09:18 +03:00
parent cc9be27ab6
commit c2be44274b
3 changed files with 89 additions and 98 deletions

94
http.go
View file

@ -2,13 +2,13 @@ package main
import ( import (
"embed" "embed"
"fmt"
"html/template" "html/template"
"log"
"math"
"net" "net"
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
) )
@ -19,8 +19,8 @@ var htmlTemplate *template.Template
type clientState int type clientState int
const ( const (
INITIAL = 0 INITIAL = iota
ACTIVE = iota ACTIVE
) )
type client struct { type client struct {
@ -29,43 +29,25 @@ type client struct {
} }
var ( var (
clientCounter uint32 = 0 clients = make([]*client, 0)
clientCounterMutex sync.Mutex clientMutex sync.Mutex
clients = make(map[uint32]*client)
clientMutex sync.RWMutex
) )
func getClientID() uint32 { func deleteClient(client *client) {
clientCounterMutex.Lock() for i := 0; i < len(clients); i++ {
defer clientCounterMutex.Unlock() if clients[i] == client {
clientCounter++ clients[i] = clients[len(clients)-1]
if clientCounter == math.MaxUint32 { clients = clients[:len(clients)-1]
clientCounter = 0 return
clearClients() }
} }
return clientCounter
} }
func clearClients() { func httpServer() error {
clientMutex.Lock()
defer clientMutex.Unlock()
for _, c := range clients {
close(c.channel)
}
clients = make(map[uint32]*client)
}
func httpServer() {
var err error
htmlTemplate = template.Must(template.ParseFS(embedFS, "template.html")) htmlTemplate = template.Must(template.ParseFS(embedFS, "template.html"))
http.HandleFunc("/stream", stream) http.HandleFunc("/stream", stream)
http.HandleFunc("/", serveRoot) http.HandleFunc("/", serveRoot)
err = http.ListenAndServe(":9090", nil) return http.ListenAndServe(":9090", nil)
if err != nil {
log.Fatalln(err)
}
} }
func getInterfaceBaseIP() string { func getInterfaceBaseIP() string {
@ -107,8 +89,7 @@ func getInterfaceBaseIP() string {
func serveRoot(w http.ResponseWriter, r *http.Request) { func serveRoot(w http.ResponseWriter, r *http.Request) {
if r.RequestURI != "/" { if r.RequestURI != "/" {
w.WriteHeader(404) http.Error(w, "Not found", http.StatusNotFound)
_, _ = w.Write([]byte("404 not found"))
return return
} }
type pageData struct { type pageData struct {
@ -126,23 +107,23 @@ func serveRoot(w http.ResponseWriter, r *http.Request) {
CanvasWidth: 512, CanvasWidth: 512,
}) })
if err != nil { if err != nil {
log.Println(err) fmt.Println("Error executing HTML template:", err)
} }
} }
var streamServerRunning atomic.Bool
func streamServer() { func streamServer() {
for { if !streamServerRunning.CompareAndSwap(false, true) {
clientMutex.RLock() return
if len(clients) == 0 {
for {
if len(clients) == 0 {
clientMutex.RUnlock()
time.Sleep(1 * time.Second)
clientMutex.RLock()
} else {
break
}
} }
go func() {
for {
clientMutex.Lock()
if len(clients) == 0 {
streamServerRunning.Store(false)
clientMutex.Unlock()
return
} }
requiresInitial := false requiresInitial := false
@ -159,14 +140,13 @@ func streamServer() {
} }
dataInitial, dataUpdate := getPicture(requiresInitial, requiresUpdate) dataInitial, dataUpdate := getPicture(requiresInitial, requiresUpdate)
tmp := clients[:0]
for clientID, v := range clients { for _, v := range clients {
if v.state == INITIAL { if v.state == INITIAL {
v.state = ACTIVE v.state = ACTIVE
select { select {
case v.channel <- dataInitial: case v.channel <- dataInitial:
default: default:
continue
} }
} else { } else {
if dataUpdate != "0" { if dataUpdate != "0" {
@ -175,15 +155,17 @@ func streamServer() {
default: default:
// Client cannot keep up // Client cannot keep up
close(v.channel) close(v.channel)
delete(clients, clientID)
continue continue
} }
} }
} }
tmp = append(tmp, v)
} }
clientMutex.RUnlock() clients = tmp
clientMutex.Unlock()
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
} }
}()
} }
func stream(w http.ResponseWriter, r *http.Request) { func stream(w http.ResponseWriter, r *http.Request) {
@ -192,17 +174,17 @@ func stream(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError) http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
return return
} }
streamServer()
w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive") w.Header().Set("Connection", "keep-alive")
messageChan := make(chan string, 40) messageChan := make(chan string, 40)
id := getClientID() newClient := &client{
newClient := client{
channel: messageChan, channel: messageChan,
state: INITIAL, state: INITIAL,
} }
clientMutex.Lock() clientMutex.Lock()
clients[id] = &newClient clients = append(clients, newClient)
clientMutex.Unlock() clientMutex.Unlock()
// For when clients are removed prior to connection closed, to avoid a call to delete(clients, id) // For when clients are removed prior to connection closed, to avoid a call to delete(clients, id)
@ -212,7 +194,7 @@ func stream(w http.ResponseWriter, r *http.Request) {
<-r.Context().Done() <-r.Context().Done()
clientMutex.Lock() clientMutex.Lock()
if !channelClosedFirst { if !channelClosedFirst {
delete(clients, id) deleteClient(newClient)
} }
close(messageChan) close(messageChan)
clientMutex.Unlock() clientMutex.Unlock()

30
main.go
View file

@ -9,24 +9,32 @@ import (
"image" "image"
"image/color" "image/color"
"image/png" "image/png"
"log" "os"
"runtime"
"sync" "sync"
) )
const interfaceName = "canvas" const interfaceName = "canvas"
const handlerCount = 4
func main() { func main() {
prePopulatePixelArray() prePopulatePixelArray()
packetChan := make(chan *[]byte, 1000) packetChan := make(chan *[]byte, 1000)
for i := 0; i < handlerCount; i++ { for i := 0; i < runtime.NumCPU(); i++ {
go packetHandler(packetChan) go packetHandler(packetChan)
} }
go startInterface(packetChan) go func() {
go streamServer() err := startInterface(packetChan)
if err != nil {
fmt.Println("Interface handler error:", err)
os.Exit(0)
}
}()
fmt.Println("Kioubit ColorPing started") fmt.Println("Kioubit ColorPing started")
fmt.Println("Interface name:", interfaceName, "HTTP server port: 9090") fmt.Println("Interface name:", interfaceName, "HTTP server port: 9090")
httpServer() if err := httpServer(); err != nil {
fmt.Println("Error starting HTTP server:", err)
return
}
} }
func prePopulatePixelArray() { func prePopulatePixelArray() {
@ -45,21 +53,21 @@ var pktPool = sync.Pool{
New: func() interface{} { return make([]byte, 2000) }, New: func() interface{} { return make([]byte, 2000) },
} }
func startInterface(packetChan chan *[]byte) { func startInterface(packetChan chan *[]byte) error {
config := water.Config{ config := water.Config{
DeviceType: water.TUN, DeviceType: water.TUN,
} }
config.Name = interfaceName config.Name = interfaceName
iFace, err := water.New(config) iFace, err := water.New(config)
if err != nil { if err != nil {
log.Fatal(err) return err
} }
for { for {
packet := pktPool.Get().([]byte) packet := pktPool.Get().([]byte)
n, err := iFace.Read(packet) n, err := iFace.Read(packet)
if err != nil { if err != nil {
log.Fatal(err) return err
} }
packet = packet[:n] packet = packet[:n]
packetChan <- &packet packetChan <- &packet
@ -167,7 +175,7 @@ func getPicture(fullUpdate bool, incrementalUpdate bool) (string, string) {
buff := new(bytes.Buffer) buff := new(bytes.Buffer)
err := encoder.Encode(buff, canvasIncrementalUpdate) err := encoder.Encode(buff, canvasIncrementalUpdate)
if err != nil { if err != nil {
log.Println(err.Error()) fmt.Println("PNG encoding error:", err)
} }
incrementalUpdateResult = "event: u\ndata:" + base64.StdEncoding.EncodeToString(buff.Bytes()) + "\n\n" incrementalUpdateResult = "event: u\ndata:" + base64.StdEncoding.EncodeToString(buff.Bytes()) + "\n\n"
} }
@ -177,7 +185,7 @@ func getPicture(fullUpdate bool, incrementalUpdate bool) (string, string) {
buff := new(bytes.Buffer) buff := new(bytes.Buffer)
err := encoder.Encode(buff, canvasFullUpdate) err := encoder.Encode(buff, canvasFullUpdate)
if err != nil { if err != nil {
log.Println(err.Error()) fmt.Println("PNG encoding error:", err)
} }
fullUpdateResult = "event: u\ndata:" + base64.StdEncoding.EncodeToString(buff.Bytes()) + "\n\n" fullUpdateResult = "event: u\ndata:" + base64.StdEncoding.EncodeToString(buff.Bytes()) + "\n\n"
} }

View file

@ -103,13 +103,9 @@
</body> </body>
<script> <script>
const canvas = document.getElementById("display"); const canvas = document.getElementById("display");
const ctx = canvas.getContext("2d")
ctx.imageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
canvas.width = 1024; canvas.width = 1024;
canvas.height = 1024; canvas.height = 1024;
const ctx = canvas.getContext("2d")
async function resizeCanvas() { async function resizeCanvas() {
let desiredSize = document.documentElement.clientWidth - 20; let desiredSize = document.documentElement.clientWidth - 20;
@ -118,6 +114,11 @@
} }
canvas.style.width = desiredSize.toString() + "px"; canvas.style.width = desiredSize.toString() + "px";
canvas.style.height = desiredSize.toString() + "px"; canvas.style.height = desiredSize.toString() + "px";
// These properties get reset on resize
ctx.imageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
} }
window.onresize = resizeCanvas; window.onresize = resizeCanvas;