From 702218008ee2b6d708d6b2821cdef80736bb3224 Mon Sep 17 00:00:00 2001 From: Taylor Gerring Date: Mon, 16 Feb 2015 14:28:33 +0100 Subject: Add versioned dependencies from godep --- .../src/github.com/obscuren/qml/datatype.go | 531 +++++++++++++++++++++ 1 file changed, 531 insertions(+) create mode 100644 Godeps/_workspace/src/github.com/obscuren/qml/datatype.go (limited to 'Godeps/_workspace/src/github.com/obscuren/qml/datatype.go') diff --git a/Godeps/_workspace/src/github.com/obscuren/qml/datatype.go b/Godeps/_workspace/src/github.com/obscuren/qml/datatype.go new file mode 100644 index 000000000..875c54622 --- /dev/null +++ b/Godeps/_workspace/src/github.com/obscuren/qml/datatype.go @@ -0,0 +1,531 @@ +package qml + +// #include +// #include "capi.h" +import "C" + +import ( + "bytes" + "fmt" + "image/color" + "reflect" + "strings" + "unicode" + "unsafe" +) + +var ( + intIs64 bool + intDT C.DataType + + ptrSize = C.size_t(unsafe.Sizeof(uintptr(0))) + + nilPtr = unsafe.Pointer(uintptr(0)) + nilCharPtr = (*C.char)(nilPtr) + + typeString = reflect.TypeOf("") + typeBool = reflect.TypeOf(false) + typeInt = reflect.TypeOf(int(0)) + typeInt64 = reflect.TypeOf(int64(0)) + typeInt32 = reflect.TypeOf(int32(0)) + typeFloat64 = reflect.TypeOf(float64(0)) + typeFloat32 = reflect.TypeOf(float32(0)) + typeIface = reflect.TypeOf(new(interface{})).Elem() + typeRGBA = reflect.TypeOf(color.RGBA{}) + typeObjSlice = reflect.TypeOf([]Object(nil)) + typeObject = reflect.TypeOf([]Object(nil)).Elem() + typePainter = reflect.TypeOf(&Painter{}) + typeList = reflect.TypeOf(&List{}) + typeMap = reflect.TypeOf(&Map{}) + typeGenericMap = reflect.TypeOf(map[string]interface{}(nil)) +) + +func init() { + var i int = 1<<31 - 1 + intIs64 = (i+1 > 0) + if intIs64 { + intDT = C.DTInt64 + } else { + intDT = C.DTInt32 + } +} + +// packDataValue packs the provided Go value into a C.DataValue for +// shiping into C++ land. +// +// For simple types (bool, int, etc) value is converted into a +// native C++ value. For anything else, including cases when value +// has a type that has an underlying simple type, the Go value itself +// is encapsulated into a C++ wrapper so that field access and method +// calls work. +// +// This must be run from the main GUI thread due to the cases where +// calling wrapGoValue is necessary. +func packDataValue(value interface{}, dvalue *C.DataValue, engine *Engine, owner valueOwner) { + datap := unsafe.Pointer(&dvalue.data) + if value == nil { + dvalue.dataType = C.DTInvalid + return + } + switch value := value.(type) { + case string: + dvalue.dataType = C.DTString + cstr, cstrlen := unsafeStringData(value) + *(**C.char)(datap) = cstr + dvalue.len = cstrlen + case bool: + dvalue.dataType = C.DTBool + *(*bool)(datap) = value + case int: + if value > 1<<31-1 { + dvalue.dataType = C.DTInt64 + *(*int64)(datap) = int64(value) + } else { + dvalue.dataType = C.DTInt32 + *(*int32)(datap) = int32(value) + } + case int64: + dvalue.dataType = C.DTInt64 + *(*int64)(datap) = value + case int32: + dvalue.dataType = C.DTInt32 + *(*int32)(datap) = value + case uint64: + dvalue.dataType = C.DTUint64 + *(*uint64)(datap) = value + case uint32: + dvalue.dataType = C.DTUint32 + *(*uint32)(datap) = value + case float64: + dvalue.dataType = C.DTFloat64 + *(*float64)(datap) = value + case float32: + dvalue.dataType = C.DTFloat32 + *(*float32)(datap) = value + case *Common: + dvalue.dataType = C.DTObject + *(*unsafe.Pointer)(datap) = value.addr + case color.RGBA: + dvalue.dataType = C.DTColor + *(*uint32)(datap) = uint32(value.A)<<24 | uint32(value.R)<<16 | uint32(value.G)<<8 | uint32(value.B) + default: + dvalue.dataType = C.DTObject + if obj, ok := value.(Object); ok { + *(*unsafe.Pointer)(datap) = obj.Common().addr + } else { + *(*unsafe.Pointer)(datap) = wrapGoValue(engine, value, owner) + } + } +} + +// TODO Handle byte slices. + +// unpackDataValue converts a value shipped by C++ into a native Go value. +// +// HEADS UP: This is considered safe to be run out of the main GUI thread. +// If that changes, fix the call sites. +func unpackDataValue(dvalue *C.DataValue, engine *Engine) interface{} { + datap := unsafe.Pointer(&dvalue.data) + switch dvalue.dataType { + case C.DTString: + s := C.GoStringN(*(**C.char)(datap), dvalue.len) + // TODO If we move all unpackDataValue calls to the GUI thread, + // can we get rid of this allocation somehow? + C.free(unsafe.Pointer(*(**C.char)(datap))) + return s + case C.DTBool: + return *(*bool)(datap) + case C.DTInt64: + return *(*int64)(datap) + case C.DTInt32: + return int(*(*int32)(datap)) + case C.DTUint64: + return *(*uint64)(datap) + case C.DTUint32: + return *(*uint32)(datap) + case C.DTUintptr: + return *(*uintptr)(datap) + case C.DTFloat64: + return *(*float64)(datap) + case C.DTFloat32: + return *(*float32)(datap) + case C.DTColor: + var c uint32 = *(*uint32)(datap) + return color.RGBA{byte(c >> 16), byte(c >> 8), byte(c), byte(c >> 24)} + case C.DTGoAddr: + // ObjectByName also does this fold conversion, to have access + // to the cvalue. Perhaps the fold should be returned. + fold := (*(**valueFold)(datap)) + ensureEngine(engine.addr, unsafe.Pointer(fold)) + return fold.gvalue + case C.DTInvalid: + return nil + case C.DTObject: + // TODO Would be good to preserve identity on the Go side. See initGoType as well. + obj := &Common{ + engine: engine, + addr: *(*unsafe.Pointer)(datap), + } + if len(converters) > 0 { + // TODO Embed the type name in DataValue to drop these calls. + typeName := obj.TypeName() + if typeName == "PlainObject" { + typeName = strings.TrimRight(obj.String("plainType"), "&*") + if strings.HasPrefix(typeName, "const ") { + typeName = typeName[6:] + } + } + if f, ok := converters[typeName]; ok { + return f(engine, obj) + } + } + return obj + case C.DTValueList, C.DTValueMap: + var dvlist []C.DataValue + var dvlisth = (*reflect.SliceHeader)(unsafe.Pointer(&dvlist)) + dvlisth.Data = uintptr(*(*unsafe.Pointer)(datap)) + dvlisth.Len = int(dvalue.len) + dvlisth.Cap = int(dvalue.len) + result := make([]interface{}, len(dvlist)) + for i := range result { + result[i] = unpackDataValue(&dvlist[i], engine) + } + C.free(*(*unsafe.Pointer)(datap)) + if dvalue.dataType == C.DTValueList { + return &List{result} + } else { + return &Map{result} + } + } + panic(fmt.Sprintf("unsupported data type: %d", dvalue.dataType)) +} + +func dataTypeOf(typ reflect.Type) C.DataType { + // Compare against the specific types rather than their kind. + // Custom types may have methods that must be supported. + switch typ { + case typeString: + return C.DTString + case typeBool: + return C.DTBool + case typeInt: + return intDT + case typeInt64: + return C.DTInt64 + case typeInt32: + return C.DTInt32 + case typeFloat32: + return C.DTFloat32 + case typeFloat64: + return C.DTFloat64 + case typeIface: + return C.DTAny + case typeRGBA: + return C.DTColor + case typeObjSlice: + return C.DTListProperty + } + return C.DTObject +} + +var typeInfoSize = C.size_t(unsafe.Sizeof(C.GoTypeInfo{})) +var memberInfoSize = C.size_t(unsafe.Sizeof(C.GoMemberInfo{})) + +var typeInfoCache = make(map[reflect.Type]*C.GoTypeInfo) + +func appendLoweredName(buf []byte, name string) []byte { + var last rune + var lasti int + for i, rune := range name { + if !unicode.IsUpper(rune) { + if lasti == 0 { + last = unicode.ToLower(last) + } + buf = append(buf, string(last)...) + buf = append(buf, name[i:]...) + return buf + } + if i > 0 { + buf = append(buf, string(unicode.ToLower(last))...) + } + lasti, last = i, rune + } + return append(buf, string(unicode.ToLower(last))...) +} + +func typeInfo(v interface{}) *C.GoTypeInfo { + vt := reflect.TypeOf(v) + for vt.Kind() == reflect.Ptr { + vt = vt.Elem() + } + + typeInfo := typeInfoCache[vt] + if typeInfo != nil { + return typeInfo + } + + typeInfo = (*C.GoTypeInfo)(C.malloc(typeInfoSize)) + typeInfo.typeName = C.CString(vt.Name()) + typeInfo.metaObject = nilPtr + typeInfo.paint = (*C.GoMemberInfo)(nilPtr) + + var setters map[string]int + var getters map[string]int + + // TODO Only do that if it's a struct? + vtptr := reflect.PtrTo(vt) + + if vt.Kind() != reflect.Struct { + panic(fmt.Sprintf("handling of %s (%#v) is incomplete; please report to the developers", vt, v)) + } + + numField := vt.NumField() + numMethod := vtptr.NumMethod() + privateFields := 0 + privateMethods := 0 + + // struct { FooBar T; Baz T } => "fooBar\0baz\0" + namesLen := 0 + for i := 0; i < numField; i++ { + field := vt.Field(i) + if field.PkgPath != "" { + privateFields++ + continue + } + namesLen += len(field.Name) + 1 + } + for i := 0; i < numMethod; i++ { + method := vtptr.Method(i) + if method.PkgPath != "" { + privateMethods++ + continue + } + namesLen += len(method.Name) + 1 + + // Track setters and getters. + if len(method.Name) > 3 && method.Name[:3] == "Set" { + if method.Type.NumIn() == 2 { + if setters == nil { + setters = make(map[string]int) + } + setters[method.Name[3:]] = i + } + } else if method.Type.NumIn() == 1 && method.Type.NumOut() == 1 { + if getters == nil { + getters = make(map[string]int) + } + getters[method.Name] = i + } + } + names := make([]byte, 0, namesLen) + for i := 0; i < numField; i++ { + field := vt.Field(i) + if field.PkgPath != "" { + continue // not exported + } + names = appendLoweredName(names, field.Name) + names = append(names, 0) + } + for i := 0; i < numMethod; i++ { + method := vtptr.Method(i) + if method.PkgPath != "" { + continue // not exported + } + if _, ok := getters[method.Name]; !ok { + continue + } + if _, ok := setters[method.Name]; !ok { + delete(getters, method.Name) + continue + } + // This is a getter method + names = appendLoweredName(names, method.Name) + names = append(names, 0) + } + for i := 0; i < numMethod; i++ { + method := vtptr.Method(i) + if method.PkgPath != "" { + continue // not exported + } + if _, ok := getters[method.Name]; ok { + continue // getter already handled above + } + names = appendLoweredName(names, method.Name) + names = append(names, 0) + } + if len(names) != namesLen { + panic("pre-allocated buffer size was wrong") + } + typeInfo.memberNames = C.CString(string(names)) + + // Assemble information on members. + membersLen := numField - privateFields + numMethod - privateMethods + membersi := uintptr(0) + mnamesi := uintptr(0) + members := uintptr(C.malloc(memberInfoSize * C.size_t(membersLen))) + mnames := uintptr(unsafe.Pointer(typeInfo.memberNames)) + for i := 0; i < numField; i++ { + field := vt.Field(i) + if field.PkgPath != "" { + continue // not exported + } + memberInfo := (*C.GoMemberInfo)(unsafe.Pointer(members + uintptr(memberInfoSize)*membersi)) + memberInfo.memberName = (*C.char)(unsafe.Pointer(mnames + mnamesi)) + memberInfo.memberType = dataTypeOf(field.Type) + memberInfo.reflectIndex = C.int(i) + memberInfo.reflectGetIndex = -1 + memberInfo.reflectSetIndex = -1 + memberInfo.addrOffset = C.int(field.Offset) + membersi += 1 + mnamesi += uintptr(len(field.Name)) + 1 + if methodIndex, ok := setters[field.Name]; ok { + memberInfo.reflectSetIndex = C.int(methodIndex) + } + } + for i := 0; i < numMethod; i++ { + method := vtptr.Method(i) + if method.PkgPath != "" { + continue // not exported + } + if _, ok := getters[method.Name]; !ok { + continue // not a getter + } + memberInfo := (*C.GoMemberInfo)(unsafe.Pointer(members + uintptr(memberInfoSize)*membersi)) + memberInfo.memberName = (*C.char)(unsafe.Pointer(mnames + mnamesi)) + memberInfo.memberType = dataTypeOf(method.Type.Out(0)) + memberInfo.reflectIndex = -1 + memberInfo.reflectGetIndex = C.int(getters[method.Name]) + memberInfo.reflectSetIndex = C.int(setters[method.Name]) + memberInfo.addrOffset = 0 + membersi += 1 + mnamesi += uintptr(len(method.Name)) + 1 + } + for i := 0; i < numMethod; i++ { + method := vtptr.Method(i) + if method.PkgPath != "" { + continue // not exported + } + if _, ok := getters[method.Name]; ok { + continue // getter already handled above + } + memberInfo := (*C.GoMemberInfo)(unsafe.Pointer(members + uintptr(memberInfoSize)*membersi)) + memberInfo.memberName = (*C.char)(unsafe.Pointer(mnames + mnamesi)) + memberInfo.memberType = C.DTMethod + memberInfo.reflectIndex = C.int(i) + memberInfo.reflectGetIndex = -1 + memberInfo.reflectSetIndex = -1 + memberInfo.addrOffset = 0 + signature, result := methodQtSignature(method) + // TODO The signature data might be embedded in the same array as the member names. + memberInfo.methodSignature = C.CString(signature) + memberInfo.resultSignature = C.CString(result) + // TODO Sort out methods with a variable number of arguments. + // It's called while bound, so drop the receiver. + memberInfo.numIn = C.int(method.Type.NumIn() - 1) + memberInfo.numOut = C.int(method.Type.NumOut()) + membersi += 1 + mnamesi += uintptr(len(method.Name)) + 1 + + if method.Name == "Paint" && memberInfo.numIn == 1 && memberInfo.numOut == 0 && method.Type.In(1) == typePainter { + typeInfo.paint = memberInfo + } + } + typeInfo.members = (*C.GoMemberInfo)(unsafe.Pointer(members)) + typeInfo.membersLen = C.int(membersLen) + + typeInfo.fields = typeInfo.members + typeInfo.fieldsLen = C.int(numField - privateFields + len(getters)) + typeInfo.methods = (*C.GoMemberInfo)(unsafe.Pointer(members + uintptr(memberInfoSize)*uintptr(typeInfo.fieldsLen))) + typeInfo.methodsLen = C.int(numMethod - privateMethods - len(getters)) + + if int(membersi) != membersLen { + panic("used more space than allocated for member names") + } + if int(mnamesi) != namesLen { + panic("allocated buffer doesn't match used space") + } + if typeInfo.fieldsLen+typeInfo.methodsLen != typeInfo.membersLen { + panic("lengths are inconsistent") + } + + typeInfoCache[vt] = typeInfo + return typeInfo +} + +func methodQtSignature(method reflect.Method) (signature, result string) { + var buf bytes.Buffer + for i, rune := range method.Name { + if i == 0 { + buf.WriteRune(unicode.ToLower(rune)) + } else { + buf.WriteString(method.Name[i:]) + break + } + } + buf.WriteByte('(') + n := method.Type.NumIn() + for i := 1; i < n; i++ { + if i > 1 { + buf.WriteByte(',') + } + buf.WriteString("QVariant") + } + buf.WriteByte(')') + signature = buf.String() + + switch method.Type.NumOut() { + case 0: + // keep it as "" + case 1: + result = "QVariant" + default: + result = "QVariantList" + } + return +} + +func hashable(value interface{}) (hashable bool) { + defer func() { recover() }() + return value == value +} + +// unsafeString returns a Go string backed by C data. +// +// If the C data is deallocated or moved, the string will be +// invalid and will crash the program if used. As such, the +// resulting string must only be used inside the implementation +// of the qml package and while the life time of the C data +// is guaranteed. +func unsafeString(data *C.char, size C.int) string { + var s string + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + sh.Data = uintptr(unsafe.Pointer(data)) + sh.Len = int(size) + return s +} + +// unsafeStringData returns a C string backed by Go data. The C +// string is NOT null-terminated, so its length must be taken +// into account. +// +// If the s Go string is garbage collected, the returned C data +// will be invalid and will crash the program if used. As such, +// the resulting data must only be used inside the implementation +// of the qml package and while the life time of the Go string +// is guaranteed. +func unsafeStringData(s string) (*C.char, C.int) { + return *(**C.char)(unsafe.Pointer(&s)), C.int(len(s)) +} + +// unsafeBytesData returns a C string backed by Go data. The C +// string is NOT null-terminated, so its length must be taken +// into account. +// +// If the array backing the b Go slice is garbage collected, the +// returned C data will be invalid and will crash the program if +// used. As such, the resulting data must only be used inside the +// implementation of the qml package and while the life time of +// the Go array is guaranteed. +func unsafeBytesData(b []byte) (*C.char, C.int) { + return *(**C.char)(unsafe.Pointer(&b)), C.int(len(b)) +} -- cgit v1.2.3