eq2go/internal/udp/middleware/compressor.go

167 lines
3.8 KiB
Go

package middleware
import (
"bytes"
"compress/flate"
"io"
"sync"
)
// CompressorConfig holds configuration for compression
type CompressorConfig struct {
Level int // Compression level (1-9, flate.BestSpeed to flate.BestCompression)
MinSize int // Minimum packet size to compress
CompressionMarker byte // Byte marker to identify compressed packets
}
// DefaultCompressorConfig returns default compressor configuration
func DefaultCompressorConfig() *CompressorConfig {
return &CompressorConfig{
Level: flate.DefaultCompression,
MinSize: 128,
CompressionMarker: 0x5A, // Same as original EQ2 implementation
}
}
// Compressor implements compression middleware using DEFLATE
type Compressor struct {
config *CompressorConfig
writerPool sync.Pool
readerPool sync.Pool
bufferPool sync.Pool
closeOnce sync.Once
}
// NewCompressor creates a new compression middleware
func NewCompressor(config *CompressorConfig) *Compressor {
if config == nil {
config = DefaultCompressorConfig()
}
c := &Compressor{
config: config,
}
// Initialize writer pool
c.writerPool.New = func() interface{} {
writer, _ := flate.NewWriter(nil, config.Level)
return writer
}
// Initialize reader pool
c.readerPool.New = func() interface{} {
return flate.NewReader(nil)
}
// Initialize buffer pool
c.bufferPool.New = func() interface{} {
return &bytes.Buffer{}
}
return c
}
// ProcessOutbound implements Middleware.ProcessOutbound
func (c *Compressor) ProcessOutbound(data []byte, next func([]byte) (int, error)) (int, error) {
if len(data) < c.config.MinSize {
return next(data)
}
compressed, err := c.compress(data)
if err != nil || len(compressed) >= len(data) {
// Compression failed or didn't help - send original
return next(data)
}
return next(compressed)
}
// ProcessInbound implements Middleware.ProcessInbound
func (c *Compressor) ProcessInbound(data []byte, next func([]byte) (int, error)) (int, error) {
if len(data) < 2 {
return next(data)
}
// Check for compression marker
if data[0] != c.config.CompressionMarker {
return next(data)
}
decompressed, err := c.decompress(data[1:])
if err != nil {
// Decompression failed - pass through original
return next(data)
}
return next(decompressed)
}
func (c *Compressor) compress(data []byte) ([]byte, error) {
// Get buffer from pool
buf := c.bufferPool.Get().(*bytes.Buffer)
defer c.bufferPool.Put(buf)
buf.Reset()
// Write compression marker
buf.WriteByte(c.config.CompressionMarker)
// Get writer from pool
writer := c.writerPool.Get().(*flate.Writer)
defer c.writerPool.Put(writer)
writer.Reset(buf)
// Compress data
if _, err := writer.Write(data); err != nil {
return nil, err
}
if err := writer.Close(); err != nil {
return nil, err
}
// Return copy of compressed data
result := make([]byte, buf.Len())
copy(result, buf.Bytes())
return result, nil
}
func (c *Compressor) decompress(data []byte) ([]byte, error) {
// Get reader from pool
reader := c.readerPool.Get().(io.ReadCloser)
defer c.readerPool.Put(reader)
// Reset reader with new data
if resetter, ok := reader.(flate.Resetter); ok {
resetter.Reset(bytes.NewReader(data), nil)
} else {
reader.Close()
reader = flate.NewReader(bytes.NewReader(data))
}
defer reader.Close()
// Get buffer from pool
buf := c.bufferPool.Get().(*bytes.Buffer)
defer c.bufferPool.Put(buf)
buf.Reset()
// Decompress data
if _, err := io.Copy(buf, reader); err != nil {
return nil, err
}
// Return copy of decompressed data
result := make([]byte, buf.Len())
copy(result, buf.Bytes())
return result, nil
}
// Close implements Middleware.Close
func (c *Compressor) Close() error {
c.closeOnce.Do(func() {
// Close any active readers/writers in pools
// Note: flate writers/readers don't need explicit cleanup
})
return nil
}