Skip to content

Commit 20d36ae

Browse files
committed
Add some doc
1 parent 60af77d commit 20d36ae

File tree

2 files changed

+224
-37
lines changed

2 files changed

+224
-37
lines changed

doc/compiler.md

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
# Using the ProtoDef compiler
2+
3+
The ProtoDef compiler can convert your protocol JSON into javascript code that can read and write buffers directly instead of using the ProtoDef interpreter. Depending on the types, the expected speedups are in the range of x10 - x100.
4+
5+
## Simple usage
6+
7+
Let's take a simple ProtoDef definition and convert it to use the ProtoDef compiler:
8+
9+
ProtoDef:
10+
```javascript
11+
const ProtoDef = require('protodef').ProtoDef
12+
13+
// Create a ProtoDef instance
14+
const proto = new ProtoDef()
15+
proto.addTypes(require('./protocol.json'))
16+
17+
// Encode and decode a message
18+
const buffer = proto.createPacketBuffer('mainType', result)
19+
const result = proto.parsePacketBuffer('mainType', buffer)
20+
```
21+
22+
ProtoDef Compiler:
23+
```javascript
24+
const { ProtoDefCompiler } = require('protodef').Compiler
25+
26+
// Create a ProtoDefCompiler instance
27+
const compiler = new ProtoDefCompiler()
28+
compiler.addTypesToCompile(require('./protocol.json'))
29+
30+
// Compile a ProtoDef instance
31+
const compiledProto = await compiler.compileProtoDef()
32+
33+
// Use it as if it were a normal ProtoDef
34+
const buffer = compiledProto.createPacketBuffer('mainType', result)
35+
const result = compiledProto.parsePacketBuffer('mainType', buffer)
36+
```
37+
38+
## New datatypes
39+
40+
Like the ProtoDef interpreter, the ProtoDef compiler can be extended with custom datatypes. To register a custom type, use the `addTypes(types)` method of the ProtoDef compiler. The `types` parameter is an object with the following structure:
41+
42+
```javascript
43+
{
44+
Read: {
45+
'type1': ['native', /* implementation */],
46+
'type2': ['context', /* implementation */],
47+
'type3': ['parametrizable', /* implementation */],
48+
/* ... */
49+
},
50+
51+
Write: {
52+
'type1': ['native', /* implementation */],
53+
'type2': ['context', /* implementation */],
54+
'type3': ['parametrizable', /* implementation */],
55+
/* ... */
56+
},
57+
58+
SizeOf: {
59+
'type1': ['native', /* implementation */],
60+
'type2': ['context', /* implementation */],
61+
'type3': ['parametrizable', /* implementation */],
62+
/* ... */
63+
}
64+
}
65+
```
66+
67+
The types can be divided into 3 categories:
68+
69+
### Native Type
70+
71+
A native type is a type read or written by a function that will be called in its original context. Use this when you need access to external definitions.
72+
73+
Example:
74+
```javascript
75+
const UUID = require('uuid-1345')
76+
77+
{
78+
Read: {
79+
'UUID': ['native', (buffer, offset) => {
80+
return {
81+
value: UUID.stringify(buffer.slice(offset, 16 + offset)), // A native type can access all captured definitions
82+
size: 16
83+
}
84+
}]
85+
},
86+
Write: {
87+
'UUID': ['native', (value, buffer, offset) => {
88+
const buf = UUID.parse(value)
89+
buf.copy(buffer, offset)
90+
return offset + 16
91+
}]
92+
},
93+
SizeOf: {
94+
'UUID': ['native', 16] // For SizeOf, a native type can be a function or directly an integer
95+
}
96+
}
97+
```
98+
99+
### Context Type
100+
101+
A context type is a type that will be called in the protocol's context. It can refer to registred native types using `native.{type}()` or context types (provided and generated) using `ctx.{type}()`, but cannot access its original context.
102+
103+
Example:
104+
```javascript
105+
const originalContextDefinition = require('something')
106+
/* global ctx */
107+
{
108+
Read: {
109+
'compound': ['context', (buffer, offset) => {
110+
// originalContextDefinition.someting() // BAD: originalContextDefinition cannot be accessed in a context type
111+
const results = {
112+
value: {},
113+
size: 0
114+
}
115+
while (true) {
116+
const typ = ctx.i8(buffer, offset) // Access to a native type (that was copied in the context)
117+
if (typ.value === 0) {
118+
results.size += typ.size
119+
break
120+
}
121+
122+
const readResults = ctx.nbt(buffer, offset) // Access to a type that was compiled and placed in the context
123+
offset += readResults.size
124+
results.size += readResults.size
125+
results.value[readResults.value.name] = {
126+
type: readResults.value.type,
127+
value: readResults.value.value
128+
}
129+
}
130+
return results
131+
}]
132+
},
133+
134+
Write: {
135+
'compound': ['context', (value, buffer, offset) => {
136+
for (const key in value) {
137+
offset = ctx.nbt({
138+
name: key,
139+
type: value[key].type,
140+
value: value[key].value
141+
}, buffer, offset)
142+
}
143+
offset = ctx.i8(0, buffer, offset)
144+
return offset
145+
}]
146+
},
147+
148+
SizeOf: {
149+
'compound': ['context', (value) => {
150+
let size = 1
151+
for (const key in value) {
152+
size += ctx.nbt({
153+
name: key,
154+
type: value[key].type,
155+
value: value[key].value
156+
})
157+
}
158+
return size
159+
}]
160+
}
161+
}
162+
```
163+
164+
### Parametrized Type
165+
166+
A parametrizable type is a function that will be generated at compile time using the provided maker function.
167+
168+
Example:
169+
```javascript
170+
{
171+
Read: {
172+
'option': ['parametrizable', (compiler, type) => {
173+
let code = 'const {value} = ctx.bool(buffer, offset)\n'
174+
code += 'if (value) {\n'
175+
code += ' const { value, size } = ' + compiler.callType(type) + '\n'
176+
code += ' return { value, size: size + 1 }\n'
177+
code += '}\n'
178+
code += 'return { value: undefined, size: 1}'
179+
return compiler.wrapCode(code)
180+
}]
181+
},
182+
183+
Write: {
184+
'option': ['parametrizable', (compiler, type) => {
185+
let code = 'if (value !== null) {\n'
186+
code += ' offset = ctx.bool(1, buffer, offset)\n'
187+
code += ' offset = ' + compiler.callType('value', type) + '\n'
188+
code += '} else {\n'
189+
code += ' offset = ctx.bool(0, buffer, offset)\n'
190+
code += '}\n'
191+
code += 'return offset'
192+
return compiler.wrapCode(code)
193+
}]
194+
},
195+
196+
SizeOf: {
197+
'option': ['parametrizable', (compiler, type) => {
198+
let code = 'if (value !== null) {\n'
199+
code += ' return 1 + ' + compiler.callType('value', type) + '\n'
200+
code += '}'
201+
code += 'return 0'
202+
return compiler.wrapCode(code)
203+
}]
204+
}
205+
```

src/compiler.js

+19-37
Original file line numberDiff line numberDiff line change
@@ -16,43 +16,6 @@ class ProtoDefCompiler {
1616
this.sizeOfCompiler = new SizeOfCompiler()
1717
}
1818

19-
/**
20-
* A native type is a type read or written by a function that will be called in it's
21-
* original context.
22-
* @param {*} type
23-
* @param {*} fn
24-
*/
25-
addNativeType (type, fn) {
26-
this.readCompiler.addNativeType(type, fn)
27-
this.writeCompiler.addNativeType(type, fn)
28-
this.sizeOfCompiler.addNativeType(type, fn)
29-
}
30-
31-
/**
32-
* A context type is a type that will be called in the protocol's context. It can refer to
33-
* registred native types using native.{type}() or context type (provided and generated)
34-
* using ctx.{type}(), but cannot access it's original context.
35-
* @param {*} type
36-
* @param {*} fn
37-
*/
38-
addContextType (type, fn) {
39-
this.readCompiler.addContextType(type, fn)
40-
this.writeCompiler.addContextType(type, fn)
41-
this.sizeOfCompiler.addContextType(type, fn)
42-
}
43-
44-
/**
45-
* A parametrizable type is a function that will be generated at compile time using the
46-
* provided maker function
47-
* @param {*} type
48-
* @param {*} maker
49-
*/
50-
addParametrizableType (type, maker) {
51-
this.readCompiler.addParametrizableType(type, maker)
52-
this.writeCompiler.addParametrizableType(type, maker)
53-
this.sizeOfCompiler.addParametrizableType(type, maker)
54-
}
55-
5619
addTypes (types) {
5720
this.readCompiler.addTypes(types.Read)
5821
this.writeCompiler.addTypes(types.Write)
@@ -146,16 +109,35 @@ class Compiler {
146109
this.parameterizableTypes = {}
147110
}
148111

112+
/**
113+
* A native type is a type read or written by a function that will be called in it's
114+
* original context.
115+
* @param {*} type
116+
* @param {*} fn
117+
*/
149118
addNativeType (type, fn) {
150119
this.primitiveTypes[type] = `native.${type}`
151120
this.native[type] = fn
152121
}
153122

123+
/**
124+
* A context type is a type that will be called in the protocol's context. It can refer to
125+
* registred native types using native.{type}() or context type (provided and generated)
126+
* using ctx.{type}(), but cannot access it's original context.
127+
* @param {*} type
128+
* @param {*} fn
129+
*/
154130
addContextType (type, fn) {
155131
this.primitiveTypes[type] = `ctx.${type}`
156132
this.context[type] = fn.toString()
157133
}
158134

135+
/**
136+
* A parametrizable type is a function that will be generated at compile time using the
137+
* provided maker function
138+
* @param {*} type
139+
* @param {*} maker
140+
*/
159141
addParametrizableType (type, maker) {
160142
this.parameterizableTypes[type] = maker
161143
}

0 commit comments

Comments
 (0)