summaryrefslogtreecommitdiff
path: root/rpc.go
blob: 049f6891e118782003886ccb728b1c00c1533bbd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package tanja

import "reflect"

// Registers a pattern for each of the methods of 'obj'. The method will be
// called from ses.Dispatch() (and thus ses.Run() if you use that) whenever a
// tuple has been received for the pattern.
//
// The function f() is called for each method with as its argument the name of
// that method. The function is expected to return the pattern to be
// registered. If nil is returned instead, no pattern will be registered for
// the method.
//
// The method type must conform to the following rules:
// - It should be exported (name must start with an uppercase letter)
// - It should not have any return values
// - It may have any number of arguments, as long as their types are allowed.
// - The first argument may be of type *tanja.Message
// - If the method is variadic, its last argument must be of type ...Element
// - Any other arguments must be of a type accepted by El()
//
// Methods that do not conform to these rules can not be registered and f()
// should return nil for them (Bad Things happen if it doesn't).
//
// If the first argument is of type *tanja.Message, the corresponding message
// is passed to the method. The method is then also registered with willReply
// set to true, so it MUST eventually call Message.Close().
// If the first argument is not of type *tanja.Message, the method will have no
// way of replying to the incoming tuple and is thus registered with willReply
// set to false.
//
// Any remaining arguments are used to capture (and in a limited sense,
// validate) elements from the remainder of the received tuple. The 'remainder'
// being any elements after the pattern returned by f(). If no matching element
// exist in the received tuple or if the element can not be converted into the
// type that the method accepts, the method will not be called.
//
// If the method is variadic, any remaining elements in the tuple (after those
// captured by any previous arguments) will be passed through to the last
// argument.
//
// Returns the number of patters registered.
//
// TODO: Examples are required to really understand the use of this method.
func (s *Session) RegRPC(obj interface{}, f func(string) Tuple) int {
	objv := reflect.ValueOf(obj)
	objt := objv.Type()
	num := 0
	for i := 0; i < objt.NumMethod(); i++ {
		mm := objt.Method(i)
		// get pattern
		pat := f(mm.Name)
		if pat == nil {
			continue
		}
		// Determine how many elements to capture, and pad that many nil's to
		// the pattern. (Ensures we only match tuples with enough elements)
		mv := objv.Method(i)
		mt := mv.Type()
		offset := len(pat)
		willReply := false
		j := 0
		if mt.NumIn() > 0 && mt.In(0) == reflect.TypeOf((*Message)(nil)) {
			willReply = true
			j++
		}
		end := mt.NumIn()
		if mt.IsVariadic() {
			end--
		}
		for ; j < end; j++ {
			pat = append(pat, El(nil))
		}
		// Generate callback and register
		s.Registert(willReply, pat).Callback(regRPCGenCallback(mt, mv, offset, willReply))
		num++
	}
	return num
}

// Should be mostly the reverse of El(). Returns the zero value if the type is
// not supported or the element could not be converted.
func regRPCValue(at reflect.Type, el Element) (val reflect.Value) {
	// Matching should be on 'at' itself rather than it's kind, since the type
	// should really be a basic one. But I suspect that this is faster, as
	// reflect.TypeOf() calls are probably not done at compile-time.
	switch at.Kind() {
	case reflect.Struct:
		if at == reflect.TypeOf(Element{nil}) {
			val = reflect.ValueOf(el)
		}
	case reflect.String:
		if v, ok := el.IsString(); ok {
			val = reflect.New(at).Elem()
			val.SetString(v)
		}
	case reflect.Bool:
		val = reflect.ValueOf(el.Bool())
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if v, ok := el.IsInt(); ok {
			val = reflect.New(at).Elem()
			val.SetInt(v)
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		if v, ok := el.IsInt(); ok && v >= 0 {
			val = reflect.New(at).Elem()
			val.SetUint(uint64(v))
		}
	case reflect.Float32, reflect.Float64:
		if v, ok := el.IsFloat(); ok {
			val = reflect.New(at).Elem()
			val.SetFloat(v)
		}
	case reflect.Slice:
		if v, ok := el.E.([]Element); ok && at == reflect.TypeOf(el.E) {
			val = reflect.ValueOf(v)
		}
	case reflect.Map:
		if v, ok := el.E.(map[string]Element); ok && at == reflect.TypeOf(el.E) {
			val = reflect.ValueOf(v)
		}
	}
	return
}

func regRPCGenCallback(mt reflect.Type, mv reflect.Value, offset int, willReply bool) func(*Message) {
	return func(msg *Message) {
		args := make([]reflect.Value, mt.NumIn())
		arg := 0
		if willReply {
			args[0] = reflect.ValueOf(msg)
			arg++
		}
		off := offset
		end := mt.NumIn()
		if mt.IsVariadic() {
			end--
		}
		for arg < end {
			args[arg] = regRPCValue(mt.In(arg), msg.Tup[off])
			if !args[arg].IsValid() {
				msg.Close()
				return
			}
			off++
			arg++
		}
		if mt.IsVariadic() {
			args[arg] = reflect.ValueOf(msg.Tup[off:])
			mv.CallSlice(args)
		} else {
			mv.Call(args)
		}
	}
}