more util types
This commit is contained in:
52
cmd/packetizer/codec.go.tmpl
Normal file
52
cmd/packetizer/codec.go.tmpl
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{{- /*gotype: github.com/Tnze/go-mc/cmd/packetizer.PackageInfo*/ -}}
|
||||||
|
// Code generated by packetizer.go; DO NOT EDIT.
|
||||||
|
|
||||||
|
package {{ .Name }}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
{{- if .Imports }}
|
||||||
|
{{ range .Imports }}
|
||||||
|
"{{ . }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
)
|
||||||
|
{{ range .Structs }}
|
||||||
|
func (c *{{ .Name }}) ReadFrom(r io.Reader) (int64, error) {
|
||||||
|
{{- if eq (len .Fields) 0 }}
|
||||||
|
return 0, nil
|
||||||
|
{{- else }}
|
||||||
|
var n int64
|
||||||
|
var err error
|
||||||
|
var temp int64
|
||||||
|
{{- range .Fields }}
|
||||||
|
temp, err = {{ generateTarget . }}.ReadFrom(r)
|
||||||
|
n += temp
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
{{- end }}
|
||||||
|
return n, err
|
||||||
|
{{- end }}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *{{ .Name }}) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
{{- if eq (len .Fields) 0 }}
|
||||||
|
return 0, nil
|
||||||
|
{{- else }}
|
||||||
|
var n int64
|
||||||
|
var err error
|
||||||
|
var temp int64
|
||||||
|
{{- range .Fields }}
|
||||||
|
temp, err = {{ generateTarget . }}.WriteTo(w)
|
||||||
|
n += temp
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
{{- end }}
|
||||||
|
return n, err
|
||||||
|
{{- end }}
|
||||||
|
}
|
||||||
|
{{ end }}
|
14
cmd/packetizer/go.mod
Normal file
14
cmd/packetizer/go.mod
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module github.com/Tnze/go-mc/cmd/packetizer
|
||||||
|
|
||||||
|
go 1.24
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Tnze/go-mc v1.20.2
|
||||||
|
golang.org/x/tools v0.34.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
golang.org/x/mod v0.25.0 // indirect
|
||||||
|
golang.org/x/sync v0.15.0 // indirect
|
||||||
|
)
|
10
cmd/packetizer/go.sum
Normal file
10
cmd/packetizer/go.sum
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
github.com/Tnze/go-mc v1.20.2 h1:arHCE/WxLCxY73C/4ZNLdOymRYtdwoXE05ohB7HVN6Q=
|
||||||
|
github.com/Tnze/go-mc v1.20.2/go.mod h1:geoRj2HsXSkB3FJBuhr7wCzXegRlzWsVXd7h7jiJ6aQ=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
|
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||||
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
|
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
361
cmd/packetizer/main.go
Normal file
361
cmd/packetizer/main.go
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/net/packet"
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FieldInfo struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
NeedConvert bool
|
||||||
|
ValidField bool
|
||||||
|
IsSlice bool
|
||||||
|
IsUnconvertible bool
|
||||||
|
IsFunc bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructInfo struct {
|
||||||
|
Name string
|
||||||
|
Package string
|
||||||
|
Fields []FieldInfo
|
||||||
|
Imports []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PackageInfo struct {
|
||||||
|
File *ast.File
|
||||||
|
Pkg *packages.Package
|
||||||
|
CommentMap ast.CommentMap
|
||||||
|
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
|
||||||
|
Structs []StructInfo
|
||||||
|
Imports []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTag(tag string) string {
|
||||||
|
if tag == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
tag = strings.Trim(tag, "`")
|
||||||
|
|
||||||
|
parts := strings.Split(tag, " ")
|
||||||
|
for _, part := range parts {
|
||||||
|
if strings.HasPrefix(part, "mc:") {
|
||||||
|
mcValue := strings.Trim(part[3:], `"`)
|
||||||
|
return mcValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldProcessStruct(commentMap ast.CommentMap, genDecl *ast.GenDecl) bool {
|
||||||
|
if groups, ok := commentMap[genDecl]; ok {
|
||||||
|
for _, cg := range groups {
|
||||||
|
for _, c := range cg.List {
|
||||||
|
content := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(c.Text), "//"))
|
||||||
|
if content == "codec:gen" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func analyzeFile(pkgInfo *PackageInfo) {
|
||||||
|
var pkField *types.Interface
|
||||||
|
typ, ok := packetFieldMap["Field"]
|
||||||
|
if ok {
|
||||||
|
pkField = typ.Underlying().(*types.Interface)
|
||||||
|
}
|
||||||
|
for _, decl := range pkgInfo.File.Decls {
|
||||||
|
gen, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok || gen.Tok != token.TYPE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !shouldProcessStruct(pkgInfo.CommentMap, gen) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, spec := range gen.Specs {
|
||||||
|
ts, ok := spec.(*ast.TypeSpec)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
st, ok := ts.Type.(*ast.StructType)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
structInfo := StructInfo{
|
||||||
|
Name: ts.Name.Name,
|
||||||
|
Package: pkgInfo.Pkg.Name,
|
||||||
|
Fields: []FieldInfo{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, field := range st.Fields.List {
|
||||||
|
if len(field.Names) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range field.Names {
|
||||||
|
fi := FieldInfo{
|
||||||
|
Name: name.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
mcTag := parseTag(fieldTagValue(field))
|
||||||
|
if mcTag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
obj := pkgInfo.Pkg.TypesInfo.Defs[name]
|
||||||
|
if obj != nil {
|
||||||
|
t := obj.Type()
|
||||||
|
|
||||||
|
if s, ok := t.(*types.Slice); ok {
|
||||||
|
fi.IsSlice = true
|
||||||
|
t = s.Elem()
|
||||||
|
if mcTag != "" {
|
||||||
|
if fieldType, ok := packetFieldMap[mcTag]; ok {
|
||||||
|
if _, ok := fieldType.Underlying().(*types.Slice); ok {
|
||||||
|
fi.IsSlice = false
|
||||||
|
fi.NeedConvert = true
|
||||||
|
t = fieldType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b, ok := t.(*types.Basic); ok {
|
||||||
|
basicType := getBasicType(b.Kind())
|
||||||
|
if basicType != nil {
|
||||||
|
fi.NeedConvert = true
|
||||||
|
t = basicType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typeString := types.TypeString(t, func(p *types.Package) string {
|
||||||
|
return ""
|
||||||
|
})
|
||||||
|
fi.Type = typeString
|
||||||
|
|
||||||
|
if mcTag != "" {
|
||||||
|
if fieldType, ok := packetFieldMap[mcTag]; ok && types.ConvertibleTo(t, fieldType) {
|
||||||
|
fi.NeedConvert = true
|
||||||
|
fi.Type = mcTag
|
||||||
|
}
|
||||||
|
if fieldType, ok := packetFieldMap[mcTag]; ok {
|
||||||
|
if sig, ok := fieldType.Underlying().(*types.Signature); ok {
|
||||||
|
if sig.Results().Len() == 1 {
|
||||||
|
fi.IsFunc = true
|
||||||
|
fi.Type = mcTag
|
||||||
|
t = sig.Results().At(0).Type()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if types.Implements(t, pkField) || types.Implements(types.NewPointer(t), pkField) {
|
||||||
|
fi.ValidField = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
structInfo.Fields = append(structInfo.Fields, fi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgInfo.Structs = append(pkgInfo.Structs, structInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBasicType(kind types.BasicKind) types.Type {
|
||||||
|
switch kind {
|
||||||
|
case types.Bool:
|
||||||
|
return packetFieldMap["Boolean"]
|
||||||
|
case types.Int:
|
||||||
|
return packetFieldMap["Int"]
|
||||||
|
case types.Int8:
|
||||||
|
return packetFieldMap["Byte"]
|
||||||
|
case types.Int16:
|
||||||
|
return packetFieldMap["Short"]
|
||||||
|
case types.Int32:
|
||||||
|
return packetFieldMap["Int"]
|
||||||
|
case types.Int64:
|
||||||
|
return packetFieldMap["Long"]
|
||||||
|
case types.Uint8:
|
||||||
|
return packetFieldMap["UnsignedByte"]
|
||||||
|
case types.Uint16:
|
||||||
|
return packetFieldMap["UnsignedShort"]
|
||||||
|
case types.Float32:
|
||||||
|
return packetFieldMap["Float"]
|
||||||
|
case types.Float64:
|
||||||
|
return packetFieldMap["Double"]
|
||||||
|
case types.String:
|
||||||
|
return packetFieldMap["String"]
|
||||||
|
case types.UntypedBool:
|
||||||
|
return packetFieldMap["Boolean"]
|
||||||
|
case types.UntypedInt:
|
||||||
|
return packetFieldMap["Int"]
|
||||||
|
case types.UntypedFloat:
|
||||||
|
return packetFieldMap["Double"]
|
||||||
|
case types.UntypedString:
|
||||||
|
return packetFieldMap["String"]
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fieldTagValue(f *ast.Field) string {
|
||||||
|
if f.Tag != nil {
|
||||||
|
return f.Tag.Value
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateFieldTarget(field FieldInfo) string {
|
||||||
|
pattern := fmt.Sprintf("(&c.%s)", field.Name)
|
||||||
|
if field.IsSlice {
|
||||||
|
pattern = "pk.Array" + pattern
|
||||||
|
} else if field.IsFunc {
|
||||||
|
pattern = "pk." + field.Type + pattern
|
||||||
|
} else if field.NeedConvert {
|
||||||
|
pattern = "(*pk." + field.Type + ")" + pattern
|
||||||
|
}
|
||||||
|
return pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按包分組
|
||||||
|
func groupByPackage(packages []*PackageInfo) map[string]*PackageInfo {
|
||||||
|
grouped := make(map[string]*PackageInfo)
|
||||||
|
|
||||||
|
for _, pkg := range packages {
|
||||||
|
key := filepath.Dir(pkg.Path)
|
||||||
|
if existing, ok := grouped[key]; ok {
|
||||||
|
existing.Structs = append(existing.Structs, pkg.Structs...)
|
||||||
|
for _, imp := range pkg.Imports {
|
||||||
|
found := false
|
||||||
|
for _, existingImp := range existing.Imports {
|
||||||
|
if existingImp == imp {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
existing.Imports = append(existing.Imports, imp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
grouped[key] = pkg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return grouped
|
||||||
|
}
|
||||||
|
|
||||||
|
var packetFieldMap = map[string]types.Type{}
|
||||||
|
|
||||||
|
//go:embed codec.go.tmpl
|
||||||
|
var codecTemplate string
|
||||||
|
|
||||||
|
var tmpl *template.Template
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
_ = packet.Field(nil)
|
||||||
|
|
||||||
|
funcMap := template.FuncMap{
|
||||||
|
"generateTarget": generateFieldTarget,
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl = template.Must(template.New("codecs").Funcs(funcMap).Parse(codecTemplate))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dir := flag.String("dir", ".", "input directory to search for codec:gen tags")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
cfg := &packages.Config{
|
||||||
|
Mode: packages.NeedName | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedFiles | packages.NeedDeps,
|
||||||
|
Fset: token.NewFileSet(),
|
||||||
|
Dir: *dir,
|
||||||
|
Env: os.Environ(),
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgs, err := packages.Load(cfg, "github.com/Tnze/go-mc/net/packet", "./...")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: failed to load packet package: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
if pkg.PkgPath == "github.com/Tnze/go-mc/net/packet" {
|
||||||
|
scope := pkg.Types.Scope()
|
||||||
|
for _, name := range scope.Names() {
|
||||||
|
packetFieldMap[name] = scope.Lookup(name).Type()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var infos []*PackageInfo
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
if pkg.PkgPath == "github.com/Tnze/go-mc/net/packet" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, file := range pkg.Syntax {
|
||||||
|
pf := cfg.Fset.Position(file.Pos()).Filename
|
||||||
|
if strings.HasSuffix(pf, "codecs.go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
info := &PackageInfo{File: file, Pkg: pkg, CommentMap: ast.NewCommentMap(cfg.Fset, file, file.Comments), Name: pkg.Name, Path: pf}
|
||||||
|
analyzeFile(info)
|
||||||
|
if info.Structs == nil || len(info.Structs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
infos = append(infos, info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(infos) == 0 {
|
||||||
|
fmt.Println("No structs found with // codec:gen. Nothing to generate.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
grouped := groupByPackage(infos)
|
||||||
|
fmt.Printf("Processing %d package groups...\n", len(grouped))
|
||||||
|
|
||||||
|
for dirPath, pkgInfo := range grouped {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := tmpl.Execute(&buf, pkgInfo); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Template execution error: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
out := filepath.Join(dirPath, "codecs.go")
|
||||||
|
if err := os.WriteFile(out, buf.Bytes(), 0644); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", out, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("Generated %s\n", out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Code generation complete.")
|
||||||
|
}
|
@ -248,6 +248,99 @@ func (o OptionEncoder[T]) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
return n1 + n2, err
|
return n1 + n2, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IDSet struct {
|
||||||
|
TagName Identifier
|
||||||
|
IDs []int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IDSet) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
if i.TagName != "" {
|
||||||
|
n1, err := VarInt(0).WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
return n1, err
|
||||||
|
}
|
||||||
|
n2, err := i.TagName.WriteTo(w)
|
||||||
|
return n1 + n2, err
|
||||||
|
} else {
|
||||||
|
n1, err := VarInt(len(i.IDs) + 1).WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
return n1, err
|
||||||
|
}
|
||||||
|
n2 := int64(0)
|
||||||
|
for _, id := range i.IDs {
|
||||||
|
temp, err := VarInt(id).WriteTo(w)
|
||||||
|
n2 += temp
|
||||||
|
if err != nil {
|
||||||
|
return n1 + n2, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n1 + n2, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IDSet) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var v VarInt
|
||||||
|
n1, err := v.ReadFrom(r)
|
||||||
|
if err != nil {
|
||||||
|
return n1, err
|
||||||
|
}
|
||||||
|
if v == 0 {
|
||||||
|
n2, err := i.TagName.ReadFrom(r)
|
||||||
|
return n1 + n2, err
|
||||||
|
}
|
||||||
|
|
||||||
|
i.IDs = make([]int32, int(v-1))
|
||||||
|
var d VarInt
|
||||||
|
for j := 0; j < int(v-1); j++ {
|
||||||
|
n2, err := d.ReadFrom(r)
|
||||||
|
if err != nil {
|
||||||
|
return n1 + n2, err
|
||||||
|
}
|
||||||
|
n1 += n2
|
||||||
|
i.IDs[j] = int32(n1)
|
||||||
|
}
|
||||||
|
return n1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptID[T FieldEncoder, P fieldPointer[T]] struct {
|
||||||
|
Has bool
|
||||||
|
ID int32
|
||||||
|
Val T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OptID[T, P]) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
if o.Has {
|
||||||
|
o.ID++
|
||||||
|
}
|
||||||
|
n1, err := (*VarInt)(&o.ID).WriteTo(w)
|
||||||
|
if err != nil || o.ID > 0 {
|
||||||
|
return n1, err
|
||||||
|
}
|
||||||
|
n2, err := o.Val.WriteTo(w)
|
||||||
|
return n1 + n2, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OptID[T, P]) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
n1, err := (*VarInt)(&o.ID).ReadFrom(r)
|
||||||
|
if o.ID > 0 {
|
||||||
|
o.ID--
|
||||||
|
return n1, err
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return n1, err
|
||||||
|
}
|
||||||
|
n2, err := P(&o.Val).ReadFrom(r)
|
||||||
|
return n1 + n2, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer returns the pointer of Val if Has is true, otherwise return nil.
|
||||||
|
func (o *OptID[T, P]) Pointer() (p *T) {
|
||||||
|
if o.Has {
|
||||||
|
p = &o.Val
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type Tuple []any // FieldEncoder, FieldDecoder or both (Field)
|
type Tuple []any // FieldEncoder, FieldDecoder or both (Field)
|
||||||
|
|
||||||
// WriteTo write Tuple to io.Writer, panic when any of filed don't implement FieldEncoder
|
// WriteTo write Tuple to io.Writer, panic when any of filed don't implement FieldEncoder
|
||||||
|
Reference in New Issue
Block a user