bitbucket.org/espinpro/go-engine
[TOC]
go-engine
go-engine is a lightweight framework to build any server-side applications. This library uses the concept of storing all vendors inside to prevent downloading extra data for each deploy, and avoid an unexpected problem with external repositories.
Package config
Example of using envconfig
// We could have a prefix for all specific configs
const EnvPrefix = "TEST"
// We should made structure whith expected configurations:
type ExampleConfig struct {
SomeString string `default:"test"` // we could configure how to parse env variable, and set default configs. This string will use TEST_SOMESTRING variable
SomeInt int `split_words:"true" default:"1"` // Will be available as TEST_SOME_INT (it`s not TEST_SOMEINT, because we set split_words option)
}
// Then we can make fucntion for init that config:
func GetConnectionConfigFromEnv() (*ConnectionConfig, error) {
c := new(ConnectionConfig)
err := envconfig.Process(EnvPrefix, c)
return c, err
}
Example of using a config
package with viper:
Let’s say we have a config server.yaml
:
SERVER_ADDR: "127.0.0.1:30002"
GOPS_ADDR: ":30003"
REGISTRY_ADDR: ":30004"
READ_TIMEOUT: 0
WRITE_TIMEOUT: 0
And we want to support file and env variables at the same time, and we should make the file as below:
// NameServerConfig contains filename for server config
const NameServerConfig = "server"
func init() {
_, err := config.GetConfigManager().Add(NewConfig(NameServerConfig)).Load(NameServerConfig) // Load configuration
if err != nil {
fastlog.Error("Error initialize server config", "err", err)
}
}
// Config contains all expected data
type Config struct {
gopsAddr string
addr string
registryAddr string
readTimeout time.Duration
writeTimeout time.Duration
filename string
}
// Filename return filename
func (s *Config) Filename() string {
return s.filename
}
// Build build config from viper
func (s *Config) Build(v *viper.Viper) error {
s.addr = v.GetString("SERVER_ADDR")
s.gopsAddr = v.GetString("GOPS_ADDR")
s.registryAddr = v.GetString("REGISTRY_ADDR")
s.readTimeout = v.GetDuration("READ_TIMEOUT")
s.writeTimeout = v.GetDuration("WRITE_TIMEOUT")
if s.addr == "" {
return ErrNotFoundAddr
}
if s.gopsAddr == "" {
return ErrNotFoundGopsAddr
}
return nil
}
// GetServerConfig get server config from manager
func GetServerConfig(mgr *config.Manager) (*Config, error) {
c, err := mgr.Get(NameServerConfig)
if err != nil {
return nil, err
}
cnf, ok := c.(*Config)
if !ok {
return nil, config.ErrNotFoundConfig
}
return cnf, nil
}
And then configuration can be used as below:
config, err := GetServerConfig(GetConfigManager())
if err != nil {
return err
}
// Use config
Package dataconv
That package contains helpers for encoding/decoding any simple data to binary format.
For example (taken from tests):
data := make([]byte, 27)
offset := EncodeUint8(data, 1, 0)
offset = EncodeUint16(data, 1245, offset)
offset = EncodeUint32(data, 1245678, offset)
offset = EncodeUint64(data, 124567891011, offset)
offset = EncodeBytes(data, []byte("test"), offset)
offset = EncodeFloat64(data, 12.56, offset)
testutils.Equal(t, offset, 27)
tuint8, offset := DecodeUint8(data, 0)
testutils.Equal(t, tuint8, uint8(1))
tuint16, offset := DecodeUint16(data, offset)
testutils.Equal(t, tuint16, uint16(1245))
tuint32, offset := DecodeUint32(data, offset)
testutils.Equal(t, tuint32, uint32(1245678))
tuint64, offset := DecodeUint64(data, offset)
testutils.Equal(t, tuint64, uint64(124567891011))
tbytes, offset := DecodeBytes(data, offset, 4)
testutils.Equal(t, tbytes, []byte("test"))
tfloat64, _ := DecodeFloat64(data, offset)
testutils.Equal(t, tfloat64, float64(12.56))
Also, that package contains helpers for check float on equal IsFloatsEqual
.
Package datatypes
Contains following datatypes:
- DataSet (where key it`s interfaces) - for any type
- DataSetStrings (where key this is strings) - for strings
- DataSetInts (where keys it`s integers) - for integers
- MultiUint32Set (as a map[uint32]map[uint32]struct{}) - for specific cases, for example, an object with id (uint32) can collide with other items, and we want to connect each collided id with all other collided ids.
Package ECS
It’s a golang implementation of ECS (Entity Component System) architecture. A beneficial pattern for video games and can be useful not only for game development. The base idea of that architecture it’s to split logic from data.
For example, we make a game, and this game has an entity
user.
type User struct {
}
But the entity does not have any function and attributes. All required details we can build-in via components
. Let’s imagine the user has a gun with one bullet, in that case, and we will have the next component:
type Gun struct {
bullets int
}
func (g *Gun) Shoot() error {
if g.bullets <= 0 {
return errors.New("no bullets")
}
g.bullets--
return nil
}
And we can build-in that component inside the user:
type User struct {
*Gun
}
Then we can put all users in memory (or storage). But we still do not shoot. To start shooting, we still want to implement a system
. The system always should have at least one function Update(ctx context.Context) error
, that function usually called each tick, or whenever we want
Lets make system
:
type ShootSystem struct {
guns []*Gun
}
func (s *ShootSystem) Shoot(u *Gun) {
s.guns = append(s.guns, u)
}
// Over context can be provided details about tick
func (s *ShootSystem) Update(ctx context.Context) error {
for _, g := range s.shooters {
err := g.Shoot()
if err != nil {
fastlog.Errorw("Error shooting", "err", err)
}
}
}
WARNING! The example above is simplified and does not thread-safe.
Package engine
It contains useful prepared structures for use with ECS.
Package errors
Contains const based error implementation
Package fastlog
Contains wrapper around the zap logger (provide compatibility to replace logrus)
Package fastserver
Contains server implementation based on github.com/valyala/fasthttp
. Contains routing implementation:
i := Root()
i.Add("/address/:name/:id/:type", NewRoute("", hello))
i.Add("/address/:name/:id", NewRoute(http.MethodGet, hello2))
i.Add("/s/*", NewRoute(http.MethodPost, hello3))
Package geometry
Contains the implementation of different algorithms:
- astar
- gjk+epa 2d
- gjk+epa 3d
- quickhull
- voronoi
Also, it contains helpers to work with a hexagon (inside hexagon
package).
And contains its implementation for simple shapes and helpers to work with them (inside shapes
package):
- border
- box (rectangle in 2s)
- line
- multiobject
- orbit
- point
- polyhedron (polygon in 2d)
- quaternion
- sphere
- triangle
Package GRPC
Contains the simple implementation of grpc notification server
Package instance
Contains single function to return unique instance id (unique per run application)
Package JWT
Wrapper around github.com/dgrijalva/jwt-go
and contains helpers to validate users.
Package machilearning
Deep learning implementation on golang. Use MongoDB for store events and models, and uses github.com/patrikeh/go-deep
for building a neural network. Provide functionality to learn any selected model.
Package mongodb
Helper to work with MongoDB. Based on go.mongodb.org/mongo-driver
driver.
Package nsq
Contains implementation for working with nsq as listener and consumer
Package order
Helper store some data in Redis (and return a unique id for that data), and helper get that data back.
Package oslistener
Provide functionality to listen to system signals and subscribe to them (Useful for correct finish process, before close application, or for reloading file-based configs)
Package physics
Contains the basic implementation of physics algorithms
Package redis
Contains helper to work with Redis
Package registry
The registry is our custom in-memory storage. The implementation contains different indexes as RTree, SkipList, Hash Map. And the registry based on groups to minimize locks.
For RTree it uses go-engine/geometry
library.
Registry contains global variable, but also could be initialized.
Example of usage:
r := NewRegistry()
err := r.Set("something", r.NextID(), data) // data it`s interface. If data implement Constructable interface, set will call Construct method. If data implement shapes.Spatial interface, set will add data to RTree index
// If first argument implement interface DataKey, Set will automatically take key of group by calling Key() function
#### How to use RTree:
// We should have an object what implement shapes.Spatial interface
type Thing struct {
shapes.Sphere
}
func NewThing(where shapes.Sphere) *Thing {
return &Thing{
Sphere: where,
}
}
func (t *Thing) SetNewSpatial(s shapes.Spatial) {
t.Sphere = s.(shapes.Sphere)
}
func (t *Thing) Bounds() shapes.Box {
return t.Sphere.Bounds()
}
func (t *Thing) UpdateSpatial(s shapes.Spatial) {
t.Sphere = s.(shapes.Sphere)
}
Inside registry we initialize rtree as rt := NewRTree(25, 50)
, and we can use it outside the registry:
rt := NewRTree(25, 50)
// Prepare 5 items what implements shapes.Spatial
t1 := NewThing(shapes.NewSphere(shapes.NewPoint(1, 1), 100))
t2 := NewThing(shapes.NewSphere(shapes.NewPoint(0.5, 1), 40))
t3 := NewThing(shapes.NewSphere(shapes.NewPoint(100, 0), 10))
t4 := NewThing(shapes.NewSphere(shapes.NewPoint(120, 0), 10))
t5 := NewThing(shapes.NewSphere(shapes.NewPoint(125, 0), 20))
// Insert 5 items into rtree (in case of using registry it will be automatically happens when Set function called):
rt.Insert(t1)
rt.Insert(t2)
rt.Insert(t3)
rt.Insert(t4)
rt.Insert(t5)
// Looking for intersect objects (return not exactly collision, collisons should be checked we gjk+epa, return objects what collided by bounding boxes)
results := rt.SearchIntersect(t2.Bounds())
// In case of using registry, it could be called as:
GetGroup("something").GetRTree().SearchIntersect(t2.Bounds())
// Getting nearest neighbors
sp := rt.NearestNeighbors(10, shapes.NewPoint(154, 105), 46)
// Delete objects from RTree
rt.Delete(t1)
rt.Delete(t2)
rt.Delete(t3)
rt.Delete(t5)
rt.Delete(t4)
rt.Delete(t6)
rt.Delete(t7)
rt.Delete(t8)
rt.Delete(t9)
rt.Delete(t10
Package rmq
Contains implementation for working with rmq as listener and consumer
Package sdb
Helper to work with PostgreSQL. Uses github.com/jmoiron/sqlx
Package server
Implementation of the native server with custom routing
Package session
Helper to work with cookies.
Package sse
Implementation of server-side-event on golang
Package template
Helper to build server-side templates for return to the client
Package testutils
Contains helpers to test code
Package ticker
Contains the implementation of re-usable ticker
Package utils
Contains random helpers to work with numbers, strings, etc
Version convention
Each release has version: vX.Y.Z or vX.Y.Z-T.N
Where: - X - significant changes (broke incompatibility); - Y - minor changes (add the new feature with compatibility), safe for migrating from y to highest y; - Z - hot-fix or path (this is minimal changes, without new features); - T - can be beta or alpha - N - contains an incremental number for beta and alpha releases
For example, next examples is correct:
- v1.0.0-alpha
- v1.0.0-beta
- v1.0.0-beta.1
- v1.0.0-beta.2
- v1.0.0
- v1.0.1