init(server/game): rewrite the server in c++
This commit is contained in:
1 parent
63385b7e87
commit
04b14f5a22
273 files changed
+3450394
No files matched your search
@@ -0,0 +1,512 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generates C++ message structs for the packets listed in proto/seeds.txt.
|
||||
|
||||
protoc parses the .proto set into a descriptor set; we walk it from the seeds,
|
||||
close over every referenced type and emit plain structs plus wire codecs.
|
||||
Generating the whole 4722-message dump would be ~150 MB of C++.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from google.protobuf import descriptor_pb2
|
||||
|
||||
F = descriptor_pb2.FieldDescriptorProto
|
||||
|
||||
CPP_KEYWORDS = {
|
||||
"alignas", "alignof", "and", "asm", "auto", "bitand", "bitor", "bool", "break", "case",
|
||||
"catch", "char", "class", "compl", "concept", "const", "consteval", "constexpr", "continue",
|
||||
"co_await", "co_return", "co_yield", "decltype", "default", "delete", "do", "double",
|
||||
"dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", "for",
|
||||
"friend", "goto", "if", "inline", "int", "long", "mutable", "namespace", "new", "noexcept",
|
||||
"not", "nullptr", "operator", "or", "private", "protected", "public", "register",
|
||||
"reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static",
|
||||
"static_assert", "static_cast", "struct", "switch", "template", "this", "throw", "true",
|
||||
"try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void",
|
||||
"volatile", "wchar_t", "while", "xor",
|
||||
}
|
||||
|
||||
SCALARS = {
|
||||
F.TYPE_DOUBLE: ("double", "Double", "0.0"),
|
||||
F.TYPE_FLOAT: ("float", "Float", "0.0f"),
|
||||
F.TYPE_INT64: ("int64_t", "Int64", "0"),
|
||||
F.TYPE_UINT64: ("uint64_t", "UInt64", "0"),
|
||||
F.TYPE_INT32: ("int32_t", "Int32", "0"),
|
||||
F.TYPE_FIXED64: ("uint64_t", "Fixed64", "0"),
|
||||
F.TYPE_FIXED32: ("uint32_t", "Fixed32", "0"),
|
||||
F.TYPE_BOOL: ("bool", "Bool", "false"),
|
||||
F.TYPE_STRING: ("std::string", "String", ""),
|
||||
F.TYPE_BYTES: ("std::string", "Bytes", ""),
|
||||
F.TYPE_UINT32: ("uint32_t", "UInt32", "0"),
|
||||
F.TYPE_SFIXED32: ("int32_t", "SFixed32", "0"),
|
||||
F.TYPE_SFIXED64: ("int64_t", "SFixed64", "0"),
|
||||
F.TYPE_SINT32: ("int32_t", "SInt32", "0"),
|
||||
F.TYPE_SINT64: ("int64_t", "SInt64", "0"),
|
||||
}
|
||||
|
||||
# wire type per field type
|
||||
WT = {
|
||||
F.TYPE_DOUBLE: 1, F.TYPE_FLOAT: 5, F.TYPE_INT64: 0, F.TYPE_UINT64: 0, F.TYPE_INT32: 0,
|
||||
F.TYPE_FIXED64: 1, F.TYPE_FIXED32: 5, F.TYPE_BOOL: 0, F.TYPE_STRING: 2, F.TYPE_BYTES: 2,
|
||||
F.TYPE_UINT32: 0, F.TYPE_SFIXED32: 5, F.TYPE_SFIXED64: 1, F.TYPE_SINT32: 0,
|
||||
F.TYPE_SINT64: 0, F.TYPE_ENUM: 0, F.TYPE_MESSAGE: 2, F.TYPE_GROUP: 3,
|
||||
}
|
||||
|
||||
PACKABLE = {
|
||||
F.TYPE_DOUBLE, F.TYPE_FLOAT, F.TYPE_INT64, F.TYPE_UINT64, F.TYPE_INT32, F.TYPE_FIXED64,
|
||||
F.TYPE_FIXED32, F.TYPE_BOOL, F.TYPE_UINT32, F.TYPE_SFIXED32, F.TYPE_SFIXED64,
|
||||
F.TYPE_SINT32, F.TYPE_SINT64, F.TYPE_ENUM,
|
||||
}
|
||||
|
||||
|
||||
def cpp_name(proto_full_name):
|
||||
""".Outer.Inner -> Outer_Inner"""
|
||||
return proto_full_name.lstrip(".").replace(".", "_")
|
||||
|
||||
|
||||
def field_name(name):
|
||||
return name + "_" if name in CPP_KEYWORDS else name
|
||||
|
||||
|
||||
class Index:
|
||||
def __init__(self, fds):
|
||||
self.messages = {} # ".Name" -> DescriptorProto
|
||||
self.enums = {} # ".Name" -> EnumDescriptorProto
|
||||
for f in fds.file:
|
||||
pkg = ("." + f.package) if f.package else ""
|
||||
for m in f.message_type:
|
||||
self._add_message(pkg, m)
|
||||
for e in f.enum_type:
|
||||
self.enums[pkg + "." + e.name] = e
|
||||
|
||||
def _add_message(self, prefix, m):
|
||||
full = prefix + "." + m.name
|
||||
self.messages[full] = m
|
||||
for n in m.nested_type:
|
||||
self._add_message(full, n)
|
||||
for e in m.enum_type:
|
||||
self.enums[full + "." + e.name] = e
|
||||
|
||||
def is_map_entry(self, type_name):
|
||||
m = self.messages.get(type_name)
|
||||
return bool(m and m.options.map_entry)
|
||||
|
||||
|
||||
def close_over(index, seeds):
|
||||
"""Transitive closure of message/enum types reachable from the seeds."""
|
||||
msgs, enums = set(), set()
|
||||
todo = list(seeds)
|
||||
while todo:
|
||||
name = todo.pop()
|
||||
if name in msgs:
|
||||
continue
|
||||
d = index.messages.get(name)
|
||||
if d is None:
|
||||
# A seed may name an enum directly: retcodes are plain uint32 on the wire,
|
||||
# so nothing reaches Retcode through a field type.
|
||||
if name in index.enums:
|
||||
enums.add(name)
|
||||
continue
|
||||
print(f" warning: unknown message {name}", file=sys.stderr)
|
||||
continue
|
||||
msgs.add(name)
|
||||
for fld in d.field:
|
||||
if fld.type == F.TYPE_MESSAGE or fld.type == F.TYPE_GROUP:
|
||||
todo.append(fld.type_name)
|
||||
elif fld.type == F.TYPE_ENUM:
|
||||
enums.add(fld.type_name)
|
||||
return msgs, enums
|
||||
|
||||
|
||||
def order_messages(index, msgs):
|
||||
"""Definition order: a struct must follow every message it holds by value."""
|
||||
ordered, state = [], {}
|
||||
|
||||
def visit(name):
|
||||
st = state.get(name)
|
||||
if st == 2:
|
||||
return
|
||||
if st == 1: # cycle: broken by Opt<T>'s indirection
|
||||
return
|
||||
state[name] = 1
|
||||
d = index.messages[name]
|
||||
for fld in d.field:
|
||||
if fld.type in (F.TYPE_MESSAGE, F.TYPE_GROUP) and fld.type_name in msgs:
|
||||
visit(fld.type_name)
|
||||
state[name] = 2
|
||||
ordered.append(name)
|
||||
|
||||
for n in sorted(msgs):
|
||||
visit(n)
|
||||
return ordered
|
||||
|
||||
|
||||
class Field:
|
||||
"""One flattened field of a message, resolved to its C++ shape."""
|
||||
|
||||
def __init__(self, index, fld, msgs):
|
||||
self.proto = fld
|
||||
self.number = fld.number
|
||||
self.name = field_name(fld.name)
|
||||
self.raw_name = fld.name
|
||||
self.type = fld.type
|
||||
self.repeated = fld.label == F.LABEL_REPEATED
|
||||
self.oneof = fld.oneof_index if fld.HasField("oneof_index") else None
|
||||
self.map = False
|
||||
self.key = None
|
||||
self.value = None
|
||||
|
||||
if self.type in (F.TYPE_MESSAGE, F.TYPE_GROUP) and index.is_map_entry(fld.type_name):
|
||||
entry = index.messages[fld.type_name]
|
||||
self.map = True
|
||||
self.repeated = False
|
||||
self.key = Field(index, entry.field[0], msgs)
|
||||
self.value = Field(index, entry.field[1], msgs)
|
||||
elif self.type in (F.TYPE_MESSAGE, F.TYPE_GROUP):
|
||||
self.msg_type = cpp_name(fld.type_name)
|
||||
elif self.type == F.TYPE_ENUM:
|
||||
self.enum_type = cpp_name(fld.type_name)
|
||||
|
||||
def elem_type(self):
|
||||
if self.type in (F.TYPE_MESSAGE, F.TYPE_GROUP):
|
||||
return self.msg_type
|
||||
if self.type == F.TYPE_ENUM:
|
||||
return self.enum_type
|
||||
return SCALARS[self.type][0]
|
||||
|
||||
def decl_type(self):
|
||||
if self.map:
|
||||
return f"std::map<{self.key.elem_type()}, {self.value.elem_type()}>"
|
||||
if self.repeated:
|
||||
return f"std::vector<{self.elem_type()}>"
|
||||
if self.type in (F.TYPE_MESSAGE, F.TYPE_GROUP):
|
||||
return f"pb::Opt<{self.msg_type}>"
|
||||
return self.elem_type()
|
||||
|
||||
def initializer(self):
|
||||
if self.map or self.repeated or self.type in (F.TYPE_MESSAGE, F.TYPE_GROUP):
|
||||
return ""
|
||||
if self.type == F.TYPE_ENUM:
|
||||
return "{}"
|
||||
return "{" + ("" if self.type in (F.TYPE_STRING, F.TYPE_BYTES) else SCALARS[self.type][2]) + "}"
|
||||
|
||||
def tag(self):
|
||||
wt = 2 if (self.map or (self.repeated and self.type in PACKABLE)) else WT[self.type]
|
||||
return (self.number << 3) | wt
|
||||
|
||||
|
||||
def gen_header(index, ordered, enums, ns):
|
||||
out = []
|
||||
w = out.append
|
||||
w("// generated by tools/gen_proto.py -- do not edit")
|
||||
w("#pragma once")
|
||||
w("#include <cstdint>")
|
||||
w("#include <map>")
|
||||
w("#include <string>")
|
||||
w("#include <vector>")
|
||||
w('#include "proto/wire.h"')
|
||||
w("")
|
||||
w(f"namespace {ns} {{")
|
||||
w("")
|
||||
|
||||
for name in sorted(enums):
|
||||
e = index.enums[name]
|
||||
w(f"enum class {cpp_name(name)} : int32_t {{")
|
||||
seen = set()
|
||||
for v in e.value:
|
||||
vn = v.name
|
||||
if vn in seen:
|
||||
continue
|
||||
seen.add(vn)
|
||||
w(f" {vn} = {v.number},")
|
||||
w("};")
|
||||
w("")
|
||||
|
||||
for name in ordered:
|
||||
w(f"struct {cpp_name(name)};")
|
||||
w("")
|
||||
|
||||
for name in ordered:
|
||||
d = index.messages[name]
|
||||
cn = cpp_name(name)
|
||||
fields = [Field(index, f, ordered) for f in d.field]
|
||||
w(f"struct {cn} {{")
|
||||
w(f' static constexpr const char* kName = "{cn}";')
|
||||
for f in fields:
|
||||
w(f" {f.decl_type()} {f.name}{f.initializer()};")
|
||||
for i, o in enumerate(d.oneof_decl):
|
||||
members = [f for f in fields if f.oneof == i]
|
||||
w(f" enum {o.name}_case_t : int32_t {{ {o.name}_NOT_SET = 0, " +
|
||||
", ".join(f"k_{m.name} = {m.number}" for m in members) + " };")
|
||||
w(f" int32_t {o.name}_case{{}};")
|
||||
w(" void encode(pb::Writer& w) const;")
|
||||
w(" bool decode(pb::Reader& r);")
|
||||
# The client reads sub-messages of a response without null-checking them and
|
||||
# throws inside its own module when one is absent, so a "blank" reply often has
|
||||
# to be blank-but-present all the way down.
|
||||
w(" void fill(int depth = 1);")
|
||||
w(" std::string serialize() const { pb::Writer w; encode(w); return w.take(); }")
|
||||
w(" bool parse(const uint8_t* p, size_t n) { pb::Reader r(p, n); return decode(r); }")
|
||||
w("};")
|
||||
w("")
|
||||
|
||||
w(f"}} // namespace {ns}")
|
||||
w("")
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def emit_write_value(w, f, expr, indent, sink="pbw"):
|
||||
pad = " " * indent
|
||||
if f.type in (F.TYPE_MESSAGE, F.TYPE_GROUP):
|
||||
w(f"{pad}{sink}.writeMessage({expr});")
|
||||
elif f.type == F.TYPE_ENUM:
|
||||
w(f"{pad}{sink}.writeInt32(static_cast<int32_t>({expr}));")
|
||||
else:
|
||||
w(f"{pad}{sink}.write{SCALARS[f.type][1]}({expr});")
|
||||
|
||||
|
||||
def emit_read_value(w, f, target, indent, src="pbr"):
|
||||
pad = " " * indent
|
||||
if f.type in (F.TYPE_MESSAGE, F.TYPE_GROUP):
|
||||
w(f"{pad}if (!{src}.readMessage({target})) return false;")
|
||||
elif f.type == F.TYPE_ENUM:
|
||||
w(f"{pad}{{ int32_t pbE; if (!{src}.readInt32(pbE)) return false; {target} = static_cast<{f.enum_type}>(pbE); }}")
|
||||
else:
|
||||
w(f"{pad}if (!{src}.read{SCALARS[f.type][1]}({target})) return false;")
|
||||
|
||||
|
||||
def default_check(f, expr):
|
||||
if f.type in (F.TYPE_STRING, F.TYPE_BYTES):
|
||||
return f"!{expr}.empty()"
|
||||
if f.type == F.TYPE_BOOL:
|
||||
return expr
|
||||
if f.type == F.TYPE_ENUM:
|
||||
return f"static_cast<int32_t>({expr}) != 0"
|
||||
if f.type in (F.TYPE_DOUBLE, F.TYPE_FLOAT):
|
||||
return f"{expr} != 0"
|
||||
return f"{expr} != 0"
|
||||
|
||||
|
||||
def gen_source(index, names, ns):
|
||||
out = []
|
||||
w = out.append
|
||||
w("// generated by tools/gen_proto.py -- do not edit")
|
||||
w('#include "proto/gen/protos.h"')
|
||||
w("")
|
||||
w(f"namespace {ns} {{")
|
||||
w("")
|
||||
|
||||
for name in names:
|
||||
d = index.messages[name]
|
||||
cn = cpp_name(name)
|
||||
fields = [Field(index, f, names) for f in d.field]
|
||||
|
||||
# ---- fill ----
|
||||
# Oneof members stay out of it: setting more than one of them is invalid.
|
||||
subs = [f for f in fields
|
||||
if f.type in (F.TYPE_MESSAGE, F.TYPE_GROUP)
|
||||
and not f.repeated and not f.map and f.oneof is None]
|
||||
w(f"void {cn}::fill(int depth) {{")
|
||||
if subs:
|
||||
w(" if (depth <= 0) return;")
|
||||
for f in subs:
|
||||
w(f" {f.name}.emplace().fill(depth - 1);")
|
||||
else:
|
||||
w(" (void)depth;")
|
||||
w("}")
|
||||
w("")
|
||||
|
||||
# ---- encode ----
|
||||
w(f"void {cn}::encode(pb::Writer& pbw) const {{")
|
||||
if not fields:
|
||||
w(" (void)pbw;")
|
||||
for f in fields:
|
||||
if f.map:
|
||||
key_guard = default_check(f.key, "kv.first")
|
||||
w(f" for (const auto& kv : {f.name}) {{")
|
||||
w(f" pbw.writeTag({f.tag()}u);")
|
||||
w(" pb::Writer pbSub;")
|
||||
w(f" if ({key_guard}) {{ pbSub.writeTag({f.key.tag()}u); ")
|
||||
emit_write_value(w, f.key, "kv.first", 12, "pbSub")
|
||||
w(" }")
|
||||
if f.value.type in (F.TYPE_MESSAGE, F.TYPE_GROUP):
|
||||
w(f" pbSub.writeTag({f.value.tag()}u);")
|
||||
emit_write_value(w, f.value, "kv.second", 8, "pbSub")
|
||||
else:
|
||||
w(f" if ({default_check(f.value, 'kv.second')}) {{ pbSub.writeTag({f.value.tag()}u); ")
|
||||
emit_write_value(w, f.value, "kv.second", 12, "pbSub")
|
||||
w(" }")
|
||||
w(" pbw.writeBytes(pbSub.data());")
|
||||
w(" }")
|
||||
elif f.repeated:
|
||||
if f.type in PACKABLE:
|
||||
w(f" if (!{f.name}.empty()) {{")
|
||||
w(f" pbw.writeTag({f.tag()}u);")
|
||||
w(" pb::Writer pbSub;")
|
||||
w(f" for (const auto& pbV : {f.name}) ")
|
||||
emit_write_value(w, f, "pbV", 12, "pbSub")
|
||||
w(" pbw.writeBytes(pbSub.data());")
|
||||
w(" }")
|
||||
else:
|
||||
w(f" for (const auto& pbV : {f.name}) {{")
|
||||
w(f" pbw.writeTag({f.tag()}u);")
|
||||
emit_write_value(w, f, "pbV", 8)
|
||||
w(" }")
|
||||
elif f.type in (F.TYPE_MESSAGE, F.TYPE_GROUP):
|
||||
w(f" if ({f.name}) {{ pbw.writeTag({f.tag()}u); pbw.writeMessage(*{f.name}); }}")
|
||||
elif f.oneof is not None:
|
||||
oname = d.oneof_decl[f.oneof].name
|
||||
w(f" if ({oname}_case == {f.number}) {{ pbw.writeTag({f.tag()}u); ")
|
||||
emit_write_value(w, f, f.name, 8)
|
||||
w(" }")
|
||||
else:
|
||||
w(f" if ({default_check(f, f.name)}) {{ pbw.writeTag({f.tag()}u); ")
|
||||
emit_write_value(w, f, f.name, 8)
|
||||
w(" }")
|
||||
w("}")
|
||||
w("")
|
||||
|
||||
# ---- decode ----
|
||||
w(f"bool {cn}::decode(pb::Reader& pbr) {{")
|
||||
w(" while (!pbr.eof()) {")
|
||||
w(" uint32_t pbTag;")
|
||||
w(" if (!pbr.readTag(pbTag)) return false;")
|
||||
w(" switch (pbTag) {")
|
||||
for f in fields:
|
||||
if f.map:
|
||||
w(f" case {f.tag()}u: {{")
|
||||
w(" pb::Reader pbSub;")
|
||||
w(" if (!pbr.readSub(pbSub)) return false;")
|
||||
w(f" {f.key.elem_type()} pbK{f.key.initializer() or '{}'};")
|
||||
w(f" {f.value.elem_type()} pbVal{f.value.initializer() or '{}'};")
|
||||
w(" while (!pbSub.eof()) {")
|
||||
w(" uint32_t pbT2;")
|
||||
w(" if (!pbSub.readTag(pbT2)) return false;")
|
||||
w(f" if (pbT2 == {f.key.tag()}u) {{")
|
||||
emit_read_value(w, f.key, "pbK", 20, "pbSub")
|
||||
w(" } else if (pbT2 == %du) {" % f.value.tag())
|
||||
emit_read_value(w, f.value, "pbVal", 20, "pbSub")
|
||||
w(" } else if (!pbSub.skip(pbT2)) return false;")
|
||||
w(" }")
|
||||
w(f" {f.name}[pbK] = std::move(pbVal);")
|
||||
w(" break;")
|
||||
w(" }")
|
||||
elif f.repeated:
|
||||
if f.type in PACKABLE:
|
||||
w(f" case {f.tag()}u: {{")
|
||||
w(" pb::Reader pbSub;")
|
||||
w(" if (!pbr.readSub(pbSub)) return false;")
|
||||
w(" while (!pbSub.eof()) {")
|
||||
w(f" {f.elem_type()} pbV{{}};")
|
||||
emit_read_value(w, f, "pbV", 16, "pbSub")
|
||||
w(f" {f.name}.push_back(pbV);")
|
||||
w(" }")
|
||||
w(" break;")
|
||||
w(" }")
|
||||
unpacked = (f.number << 3) | WT[f.type]
|
||||
w(f" case {unpacked}u: {{")
|
||||
w(f" {f.elem_type()} pbV{{}};")
|
||||
emit_read_value(w, f, "pbV", 12)
|
||||
w(f" {f.name}.push_back(pbV);")
|
||||
w(" break;")
|
||||
w(" }")
|
||||
else:
|
||||
w(f" case {f.tag()}u: {{")
|
||||
w(f" {f.elem_type()} pbV{{}};")
|
||||
emit_read_value(w, f, "pbV", 12)
|
||||
w(f" {f.name}.push_back(std::move(pbV));")
|
||||
w(" break;")
|
||||
w(" }")
|
||||
elif f.type in (F.TYPE_MESSAGE, F.TYPE_GROUP):
|
||||
w(f" case {f.tag()}u:")
|
||||
w(f" if (!pbr.readMessage({f.name}.emplace())) return false;")
|
||||
if f.oneof is not None:
|
||||
w(f" {d.oneof_decl[f.oneof].name}_case = {f.number};")
|
||||
w(" break;")
|
||||
else:
|
||||
w(f" case {f.tag()}u:")
|
||||
emit_read_value(w, f, f.name, 12)
|
||||
if f.oneof is not None:
|
||||
w(f" {d.oneof_decl[f.oneof].name}_case = {f.number};")
|
||||
w(" break;")
|
||||
w(" default:")
|
||||
w(" if (!pbr.skip(pbTag)) return false;")
|
||||
w(" break;")
|
||||
w(" }")
|
||||
w(" }")
|
||||
w(" return true;")
|
||||
w("}")
|
||||
w("")
|
||||
|
||||
w(f"}} // namespace {ns}")
|
||||
w("")
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def parse_seeds(path):
|
||||
seeds = []
|
||||
with open(path, encoding="utf-8") as fh:
|
||||
for line in fh:
|
||||
line = line.split("#", 1)[0].strip()
|
||||
if line:
|
||||
seeds.append(line)
|
||||
return seeds
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--proto-dir", required=True)
|
||||
ap.add_argument("--seeds", required=True)
|
||||
ap.add_argument("--out-dir", required=True)
|
||||
ap.add_argument("--protoc", default="protoc")
|
||||
ap.add_argument("--shards", type=int, default=8)
|
||||
ap.add_argument("--namespace", default="proto")
|
||||
args = ap.parse_args()
|
||||
|
||||
protos = sorted(f for f in os.listdir(args.proto_dir) if f.endswith(".proto"))
|
||||
tmp = tempfile.mkdtemp(prefix="capysr-proto-")
|
||||
desc = os.path.join(tmp, "all.desc")
|
||||
try:
|
||||
subprocess.run(
|
||||
[args.protoc, f"--descriptor_set_out={desc}", "-I", args.proto_dir] + protos,
|
||||
check=True,
|
||||
)
|
||||
fds = descriptor_pb2.FileDescriptorSet()
|
||||
with open(desc, "rb") as fh:
|
||||
fds.ParseFromString(fh.read())
|
||||
finally:
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
index = Index(fds)
|
||||
seeds = ["." + s for s in parse_seeds(args.seeds)]
|
||||
msgs, enums = close_over(index, seeds)
|
||||
ordered = order_messages(index, msgs)
|
||||
print(f" {len(ordered)} messages, {len(enums)} enums from {len(seeds)} seeds")
|
||||
|
||||
os.makedirs(args.out_dir, exist_ok=True)
|
||||
header = gen_header(index, ordered, enums, args.namespace)
|
||||
write_if_changed(os.path.join(args.out_dir, "protos.h"), header)
|
||||
|
||||
shards = [[] for _ in range(args.shards)]
|
||||
for i, name in enumerate(ordered):
|
||||
shards[i % args.shards].append(name)
|
||||
for i, names in enumerate(shards):
|
||||
src = gen_source(index, names, args.namespace)
|
||||
write_if_changed(os.path.join(args.out_dir, f"protos_{i}.cpp"), src)
|
||||
|
||||
|
||||
def write_if_changed(path, text):
|
||||
if os.path.exists(path):
|
||||
with open(path, encoding="utf-8") as fh:
|
||||
if fh.read() == text:
|
||||
return
|
||||
with open(path, "w", encoding="utf-8", newline="\n") as fh:
|
||||
fh.write(text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user