- 参考 https://www.jianshu.com/p/f1110b22cb5c
编码方案, Cap'n Proto编码的数据格式跟在内存里面的布局是一致的,所以可以直接将编码好的structure直接字节存放到硬盘上面。
点击(此处)折叠或打开
- 
				Cap'n Proto的编码是方案是独立于任何平台的,但在现在的CPU上面(小端序)会有更高的性能。数据的组织类似compiler组织struct:固定宽度,固定偏移,以及合适的内存对齐,对于可变的数组使用pointer嵌入,而pointer也是使用的偏移存放而不是绝对地址。整数使用的是小端序,因为多数现代CPU都是小端序的。
 
- 
				
 
- 
				其实如果熟悉C或者C++的结构体,就可以知道Cap'n Proto的编码方式就跟struct的内存布局差不多。
 
- 
				
 
- 
				
 
点击(此处)折叠或打开
- 跟Protobuf一样,Cap'n Proto也需要定义描述文件,然后通过capnp的编译器编译成特定语言的对象使用.
- 示例
- 
				@0xdbb9ad1f14bf0b36; # unique file ID, generated by `capnp id`
 
- 
				
 
- 
				struct Person {
 
- 
				  name @0 :Text;
 
- 
				  birthdate @3 :Date;
 
- 
				
 
- 
				  email @1 :Text;
 
- 
				  phones @2 :List(PhoneNumber);
 
- 
				
 
- 
				  struct PhoneNumber {
 
- 
				    number @0 :Text;
 
- 
				    type @1 :Type;
 
- 
				
 
- 
				    enum Type {
 
- 
				      mobile @0;
 
- 
				      home @1;
 
- 
				      work @2;
 
- 
				    }
 
- 
				  }
 
- 
				}
 
- 
				
 
- 
				struct Date {
 
- 
				  year @0 :Int16;
 
- 
				  month @1 :UInt8;
 
- 
				  day @2 :UInt8;
 
- 
				}
 
- 
				
 
- 
				类型是定义在名字后面的,通常来说,对于一个变量来说,我们可能{BANNED}最佳关注的是它的名字,一个好的命名,就很容易让大家知道是干啥的。譬如上面的name一看就知道是表示的用户的名字。这点跟c语言是反的,它是先类型,在变量名,不过很多后续的语言,譬如go,rust等都是先名字,再类型了。
 
- @N用来给struct里面的field进行编号,编号从0开始,而且必须是连续的(这点跟Protobuf不一样)。上面birthdate虽然看起来在email和phones的前面,但是它的编号较大,实际编码的时候会放到后面。
点击(此处)折叠或打开
- 
				使用 #进行注释,注释应该跟在定义的后面,或者新启一行:
 
- 
				struct Date {
 
- 
				  # A standard Gregorian calendar date.
 
- 
				
 
- 
				  year @0 :Int16;
 
- 
				  # The year. Must include the century.
 
- 
				  # Negative value indicates BC.
 
- 
				
 
- 
				  month @1 :UInt8; # Month number, 1-12.
 
- 
				  day @2 :UInt8; # Day number, 1-30.
 
- 
				}
 
- 
				
 
- 
				内置类型
 
- 
				Void: Void
 
- 
				Boolean: Bool
 
- 
				Integers: Int8, Int16, Int32, Int64
 
- 
				Unsigned integers: UInt8, UInt16, UInt32, UInt64
 
- 
				Floating-point: Float32, Float64
 
- 
				Blobs: Text, Data
 
- 
				Lists: List(T)
 
- 
				
 
- 
				Void只有一个可能的值,使用0 bits进行编码,通常很少使用,但是可以作为union的member。
 
- 
				Text通常是UTF-8编码的,使用NULL结尾的字符串。
 
- 
				Data是任意二进制数据。
 
- List是一个泛型类型,我们可以用特定类型去特化实现,譬如List(Int32)就是一个Int32的List
结构体其实类似于c的struct,field的有名字,有类型定义,同时需要编号:
struct Person { name @0 :Text; email @1 :Text; }
Field也可以有默认值:
foo @0 :Int32 = 123; bar @1 :Text = "blah"; baz @2 :List(Bool) = [ true, false, false, true ]; qux @3 :Person = (name = "Bob", email = "bob@example.com"); corge @4 :Void = void; grault @5 :Data = 0x"a1 40 33";
点击(此处)折叠或打开
- 
				Union是定义在struct里面同一个位置的一组fields,一次只能允许一个field被设置,我们使用不一样的tag来获知当前哪个field被设置了,不同于c里面的union,它不是类型,只是简单的fields聚合。
 
- 
				struct Person {
 
- 
				  # ...
 
- 
				
 
- 
				  employment :union {
 
- 
				    unemployed @4 :Void;
 
- 
				    employer @5 :Company;
 
- 
				    school @6 :School;
 
- 
				    selfEmployed @7 :Void;
 
- 
				    # We assume that a person is only one of these.
 
- 
				  }
 
- 
				}
 
- 
				
 
- 
				union可以没有名字,但是一个struct里面{BANNED}最佳多只能包含一个没名字的union:
 
- 
				
 
- 
				Union里面的field需要跟struct的field一起编号。
 
- 
				我们在上面的union中使用了Void类型,这个类型没有任何额外的信息,仅仅是为了跟其他状态区分。
 
- 
				通常,当一个struct初始化的时候,在union里面具有{BANNED}最佳小number field会被默认的设置,如果不想默认设置任何field,我们可以用在union里面的{BANNED}最佳小number定义一个unset的field。
 
- 
				我们可以将当前存在的field加入一个新的union,并且不会破坏当前数据的兼容性。
 
- 
				
 
- 
				
 
- 
				group将一组fields封装到特定的作用域里面:
 
- 
				
 
- 
				struct Person {
 
- 
				  # ...
 
- 
				
 
- 
				  # Note: This is a terrible way to use groups, and meant
 
- 
				  #   only to demonstrate the syntax.
 
- 
				  address :group {
 
- 
				    houseNumber @8 :UInt32;
 
- 
				    street @9 :Text;
 
- 
				    city @10 :Text;
 
- 
				    country @11 :Text;
 
- 
				  }
 
- 
				}
 
- 
				
 
- 
				group 里面的fields仍然是struct的fields,需要跟其他struct的fields一起编号。
 
- 
				
 
- group 的用法一般用在 union 里边,用于后期扩展.
点击(此处)折叠或打开
- 
				动态类型域
 
- 
				Struct可以定义field的类型为AnyPointer,类似于c里面的void*.
 
- 
				
 
- 
				枚举
 
- 
				Enum就是一组符号值的集合:
 
- 
				enum Rfc3092Variable {
 
- 
				  foo @0;
 
- 
				  bar @1;
 
- 
				  baz @2;
 
- 
				  qux @3;
 
- 
				  # ...
 
- 
				}
 
- 
				
 
- 
				Enum的成员必须从0开始编号,在c语言里面,enum通常都是数字类型的,但是在Cap'n Proto里面,它还可以是其他值
 
- 
				
 
- 
				
 
- 
				
 
- 
				Interface是一组methods的集合,各个method可以有参数,有返回值,methods也必须从0开始编号。Interface支持继承,同样也支持多继承
 
- 
				
 
- 
				interface Node {
 
- 
				  isDirectory @0 () -> (result :Bool);
 
- 
				}
 
- 
				
 
- 
				interface Directory extends(Node) {
 
- 
				  list @0 () -> (list: List(Entry));
 
- 
				  struct Entry {
 
- 
				    name @0 :Text;
 
- 
				    node @1 :Node;
 
- 
				  }
 
- 
				
 
- 
				  create @1 (name :Text) -> (file :File);
 
- 
				  mkdir @2 (name :Text) -> (directory :Directory);
 
- 
				  open @3 (name :Text) -> (node :Node);
 
- 
				  delete @4 (name :Text);
 
- 
				  link @5 (name :Text, node :Node);
 
- 
				}
 
- 
				
 
- 
				interface File extends(Node) {
 
- 
				  size @0 () -> (size: UInt64);
 
- 
				  read @1 (startAt :UInt64 = 0, amount :UInt64 = 0xffffffffffffffff)
 
- 
				       -> (data: Data);
 
- 
				  # Default params = read entire file.
 
- 
				
 
- 
				  write @2 (startAt :UInt64, data :Data);
 
- 
				  truncate @3 (size :UInt64);
 
- 
				}
 
- 
				
 
- 
				泛型
 
- 
				我们可以定义泛型的struct或者interface
 
- 
				struct Map(Key, Value) {
 
- 
				  entries @0 :List(Entry);
 
- 
				  struct Entry {
 
- 
				    key @0 :Key;
 
- 
				    value @1 :Value;
 
- 
				  }
 
- 
				}
 
- 
				
 
- 
				struct People {
 
- 
				  byName @0 :Map(Text, Person);
 
- 
				  # Maps names to Person instances.
 
- 
				}
 
- 
				定义了一个泛型的Map,然后在People里面用Text,Person作为参数来特化这个Map,如果我们了解c++的模板,就可以知道他们差不多。
 
- 
				
 
- 
				
 
- 
				泛型方法
 
- 
				interface也可以提供泛型method:
 
- 
				interface Assignable(T) {
 
- 
				  # A generic interface, with non-generic methods.
 
- 
				  get @0 () -> (value :T);
 
- 
				  set @1 (value :T) -> ();
 
- 
				}
 
- 
				
 
- 
				interface AssignableFactory {
 
- 
				  newAssignable @0 [T] (initialValue :T)
 
- 
				      -> (assignable :Assignable(T));
 
- 
				  # A generic method.
 
- 
				}
 
- 
				定义了一个泛型的interface,然后在对应的factory里面,创建这个interface的method就是泛型的method。
 
- 
				
 
- 
				
 
- 
				const来定义常量 
 
- 
				const pi :Float32 = 3.14159;
 
- 
				const bob :Person = (name = "Bob", email = "bob@example.com");
 
- 
				const secret :Data = 0x"9f98739c2b53835e 6720a00907abd42f";
 
- 
				
 
- 
				const foo :Int32 = 123;
 
- 
				const bar :Text = "Hello";
 
- 
				const baz :SomeStruct = (id = .foo, message = .bar);
 
- 
				
 
- 
				通常常量都都定义在全局scope里面,我们通过.来进行引用获取
 
- 
				
 
- 
				
 
- 
				嵌套,作用域以及别名
 
- 
				我们可以在struct或者interface里面嵌套常量,别名或者新的类型定义。
 
- 
				
 
- 
				struct Foo {
 
- 
				  struct Bar {
 
- 
				    #...
 
- 
				  }
 
- 
				  bar @0 :Bar;
 
- 
				}
 
- 
				
 
- 
				struct Baz {
 
- 
				  bar @0 :Foo.Bar;
 
- 
				}
 
- 
				
 
- 
				struct Qux {
 
- 
				  using Foo.Bar;
 
- 
				  bar @0 :Bar;
 
- 
				}
 
- 
				
 
- 
				struct Corge {
 
- 
				  using T = Foo.Bar;
 
- 
				  bar @0 :T;
 
- 
				}
 
- 
				
 
- 
				
 
- 
				import导入其他文件的类型定义
 
- 
				struct Foo {
 
- 
				  # Use type "Baz" defined in bar.capnp.
 
- 
				  baz @0 :import "bar.capnp".Baz;
 
- 
				}
 
- 
				
 
- 
				也可以直接使用using来设置别名
 
- 
				using Bar = import "bar.capnp";
 
- 
				
 
- 
				struct Foo {
 
- 
				  # Use type "Baz" defined in bar.capnp.
 
- 
				  baz @0 :Bar.Baz;
 
- 
				}
 
- 
				using import "bar.capnp".Baz;
 
- 
				
 
- 
				struct Foo {
 
- 
				  baz @0 :Baz;
 
- 
				}
 
- 
				
 
- 
				
 
- 
				唯一ID
 
- 
				每个Cap'n Proto文件都必须有唯一的一个64bit ID,使用capnp id生成。譬如{BANNED}最佳开始例子里面的file ID
 
- 
				# file ID
 
- @0xdbb9ad1f14bf0b36
