1 Star 0 Fork 0

GitHub Codes/neffos

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
reflect.go 7.97 KB
一键复制 编辑 原始数据 按行查看 历史
package neffos
import (
"reflect"
"strings"
)
func indirectType(typ reflect.Type) reflect.Type {
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
return typ
}
func isZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Struct:
zero := true
for i := 0; i < v.NumField(); i++ {
zero = zero && isZero(v.Field(i))
}
if typ := v.Type(); typ != nil && v.IsValid() {
f, ok := typ.MethodByName("IsZero")
// if not found
// if has input arguments (1 is for the value receiver, so > 1 for the actual input args)
// if output argument is not boolean
// then skip this IsZero user-defined function.
if !ok || f.Type.NumIn() > 1 || f.Type.NumOut() != 1 && f.Type.Out(0).Kind() != reflect.Bool {
return zero
}
method := v.Method(f.Index)
// no needed check but:
if method.IsValid() && !method.IsNil() {
// it shouldn't panic here.
zero = method.Call(nil)[0].Interface().(bool)
}
}
return zero
case reflect.Func, reflect.Map, reflect.Slice:
return v.IsNil()
case reflect.Array:
zero := true
for i := 0; i < v.Len(); i++ {
zero = zero && isZero(v.Index(i))
}
return zero
}
// if not any special type then use the reflect's .Zero
// usually for fields, but remember if it's boolean and it's false
// then it's zero, even if set-ed.
if !v.CanInterface() {
// if can't interface, i.e return value from unexported field or method then return false
return false
}
zero := reflect.Zero(v.Type())
return v.Interface() == zero.Interface()
}
// does not support child elements on purpose.
func visitFields(typ reflect.Type, visitor func(f reflect.StructField) bool) int {
typ = indirectType(typ)
for n, i := typ.NumField(), 0; i < n; i++ {
f := typ.Field(i)
found := visitor(f)
if found {
return i
}
}
return -1
}
func getNonZeroFields(v reflect.Value) (fields map[int]reflect.Value) {
v = reflect.Indirect(v)
visitFields(v.Type(), func(f reflect.StructField) bool {
fieldIndex := f.Index[0]
fieldValue := v.Field(fieldIndex)
if !isZero(fieldValue) {
if fields == nil {
fields = make(map[int]reflect.Value)
}
fields[fieldIndex] = fieldValue
}
return false
})
return
}
func getFieldIndex(forType reflect.Type, fieldType reflect.Type) int {
return visitFields(forType, func(f reflect.StructField) bool {
return f.Type == fieldType
})
}
func resolveStructNamespace(v reflect.Value) (string, bool) {
// By Namespace() string method.
typ := v.Type()
method, ok := typ.MethodByName("Namespace")
if ok {
if getNamespace, ok := v.Method(method.Index).Interface().(func() string); ok {
namespace := getNamespace()
Debugf("Set namespace [\"%s\"] from method [%s.%s]", func() dargs {
return dargs{namespace, nameOf(typ), method.Name}
})
return namespace, true
}
}
// By field Namespace string with filled value.
typ = indirectType(typ)
v = reflect.Indirect(v)
if f, ok := typ.FieldByNameFunc(func(s string) bool { return s == "Namespace" }); ok {
if f.Type.Kind() == reflect.String {
namespace := v.Field(f.Index[0]).String()
Debugf("Set namespace [\"%s\"] from field [%s.%s]", func() dargs {
return dargs{namespace, nameOf(typ), f.Name}
})
return namespace, true
}
}
return "", false
}
var (
nsConnType = reflect.TypeOf((*NSConn)(nil))
msgType = reflect.TypeOf(Message{})
errType = reflect.TypeOf((*error)(nil)).Elem()
)
func makeMessageHandlerFuncType(forType reflect.Type, nsConnFieldIndex int) reflect.Type {
// Create the dynamic type which methods will be compared to.
// remember, the receiver Ptr is also part of the input arguments,
// that's why we don't use a static type assertion.
expectedIn := []reflect.Type{
forType,
nsConnType,
msgType,
}
if nsConnFieldIndex >= 0 {
// Except when the Ptr is a dynamic one (has a field of NSConn) then the event callback does not require
// that on its input arguments.
expectedIn = append(expectedIn[0:1], expectedIn[2:]...)
}
return reflect.FuncOf(expectedIn, []reflect.Type{errType}, false)
}
func isArgOf(fnType reflect.Type, argType reflect.Type) bool {
if fnType.Kind() != reflect.Func {
panic("isArgOf used on a non-method type")
}
for i, n := 0, fnType.NumIn(); i < n; i++ {
if fnType.In(i) == argType {
return true
}
}
return false
}
func makeEventFromMethod(v reflect.Value, method reflect.Method, eventMatcher EventMatcherFunc) (eventName string, cb MessageHandlerFunc) {
eventName = method.Name
// if method looks like a system event, i.e
// OnNamespaceConnected, then convert its registered event name
// _OnNamespaceConnected which is the correct.
// We could accept a func like:
// func(s *myConn) _OnNamespaceConnected(msg neffos.Message) error
// but Go linting does not allow this and
// we don't want our users to have yellow boxes everywhere in their editors.
if IsSystemEvent("_" + eventName) {
eventName = "_" + eventName
}
if !IsSystemEvent(eventName) {
if eventMatcher != nil {
newName, ok := eventMatcher(method.Name)
if !ok {
return "", nil
}
eventName = newName
}
}
if isArgOf(method.Type, nsConnType) {
// it should accept NSConn - static "controller".
cb = v.Method(method.Index).Interface().(func(*NSConn, Message) error)
} else {
// the NSConn exists on the "controller" itself which is set dynamically.
cb = func(c *NSConn, msg Message) error {
// load an existing instance which contains the same "c".
return c.value.Method(method.Index).Interface().(func(Message) error)(msg)
}
}
return
}
// StructInjector is a type which injects a dynamic struct value.
// See `Struct.SetInjector` for more.
type StructInjector func(structType reflect.Type, nsConn *NSConn) (structValue reflect.Value)
func nameOf(structType reflect.Type) string {
structType = indirectType(structType)
typName := structType.Name()
pkgPath := structType.PkgPath()
fullname := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + typName
return fullname
}
func makeEventsFromStruct(v reflect.Value, eventMatcher EventMatcherFunc, injector StructInjector) Events {
events := make(Events)
typ := v.Type()
// get the index of field of a "NSConn" type.
nsConnFieldIndex := getFieldIndex(typ, nsConnType)
msgHandlerType := makeMessageHandlerFuncType(typ, nsConnFieldIndex)
for i, n := 0, typ.NumMethod(); i < n; i++ {
method := typ.Method(i)
if method.Type != msgHandlerType {
continue
}
eventName, cb := makeEventFromMethod(v, method, eventMatcher)
if cb == nil {
continue
}
Debugf("Event [\"%s\"] is handled by [%s.%s] method", func() dargs {
return dargs{eventName, nameOf(typ), method.Name}
})
events[eventName] = cb
}
if nsConnFieldIndex != -1 {
typ = indirectType(typ)
var staticFields map[int]reflect.Value
if injector == nil {
// maybe this should be added no matter what, I have to check
// some things in our company's production server first.
staticFields = getNonZeroFields(v)
DebugEach(staticFields, func(idx int, f reflect.Value) {
fval := f.Interface()
fname := typ.Field(idx).Name
if fname == "Namespace" {
// let's no log this as user field because
// it's optionally used to provide a namespace on NewStruct.GetNamespaces().
return
}
Debugf("Field [%s.%s] marked as static on value [%v]", nameOf(typ), fname, fval)
})
injector = func(typ reflect.Type, nsConn *NSConn) reflect.Value {
return reflect.New(typ)
}
}
cb, hasNamespaceConnect := events[OnNamespaceConnect]
events[OnNamespaceConnect] = func(c *NSConn, msg Message) error {
cachePtr := injector(typ, c)
cacheElem := cachePtr.Elem()
// set the NSConn dynamic field.
cacheElem.Field(nsConnFieldIndex).Set(reflect.ValueOf(c))
// set any static fields if default injector (see above).
for findex, fvalue := range staticFields {
cacheElem.Field(findex).Set(fvalue)
}
// Store it for the rest of the events inside
// this namespace of that specific connection.
c.value = cachePtr
if hasNamespaceConnect {
return cb(c, msg)
}
return nil
}
}
return events
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/github-codes/neffos.git
git@gitee.com:github-codes/neffos.git
github-codes
neffos
neffos
master

搜索帮助