blob: 863088aeecea3922e400ea28034fe731e924ca53 [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gfxapi
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"reflect"
"unicode"
"android.googlesource.com/platform/tools/gpu/api/snippets"
"android.googlesource.com/platform/tools/gpu/framework/binary"
"android.googlesource.com/platform/tools/gpu/framework/binary/cyclic"
"android.googlesource.com/platform/tools/gpu/framework/binary/registry"
"android.googlesource.com/platform/tools/gpu/framework/binary/vle"
"android.googlesource.com/platform/tools/gpu/gapid/service/path"
)
// GlobalSnippets is kindred grouped table of snippets with paths that apply
// to the global state. It is used to provide snippets for the API state
// object and ultimately the state view in the UI.
type GlobalSnippets []snippets.KindredSnippets
// loadSnippets decodes the input reader and populates the receiver with
// the decoded snippet objects. Return non-nil if there was an error reading
// or decoding. The reader is a stream of Variant of type
// snippets.KindredSnippets.
func (g *GlobalSnippets) loadSnippets(in io.Reader) error {
d := cyclic.Decoder(vle.Reader(in))
for {
obj := d.Variant()
if d.Error() != nil {
if d.Error() == io.EOF {
return nil
}
return d.Error()
}
snips, ok := obj.(snippets.KindredSnippets)
if !ok {
return fmt.Errorf("Expected snippets.KindredSnippets got %T", obj)
}
*g = append(*g, snips)
}
}
// AddStateSnippetsFromReader decodes the input reader and adds snippet objects
// to the specified entity which should be the schema entity for the API
// state object associated with the snippets. Returns an error if there
// was an error reading or decoding.
func AddStateSnippetsFromReader(stateEntity *binary.Entity, reader io.Reader) error {
var g GlobalSnippets
if err := g.loadSnippets(reader); err != nil {
return err
}
for _, snip := range g {
stateEntity.Metadata = append(stateEntity.Metadata, snip)
}
return nil
}
// AddStateSnippetsFromBase64String decodes the specified string as base64
// encoded binary decoder input. Decoded snippet objects are added to the
// specified entity which should be the schema entity for the API state object
// associated with the snippets. Returns a non-nil error if there was an
// error decoding base64 or binary decoding. Base64 encoding is used so that
// the binary encoded string can be embedded directly in the library using
// the "embed" tool.
func AddStateSnippetsFromBase64String(stateEntity *binary.Entity, b64 string) error {
buf, err := base64.StdEncoding.DecodeString(b64)
if err != nil {
return err
}
return AddStateSnippetsFromReader(stateEntity, bytes.NewBuffer(buf))
}
type seenStack []reflect.Type
func (seen seenStack) contains(t reflect.Type) bool {
for _, tt := range seen {
if t == tt {
return true
}
}
return false
}
// Generate snippets for CanFollow this information is not captured in the
// API file, but only in the Go code by the interface path.Linker.
// Consequently we generate these snippets at startup using reflection.
// addCanFollow adds CanFollow snippets for the type 't' and its children.
func addCanFollow(pth snippets.Pathway, t reflect.Type, snips *[]binary.Object, seen seenStack) {
if seen.contains(t) {
// Avoid cycles
return
}
seen = append(seen, t)
if t.Kind() == reflect.Ptr {
// Note pointer types in Go are a consequence of reference types in the
// API language, so we just dereference them.
addCanFollow(pth, t.Elem(), snips, seen)
return
}
if t.Kind() != reflect.Struct && pth == nil {
panic(fmt.Errorf("non-struct type %v at top-level", t.Name))
}
linker := reflect.TypeOf((*path.Linker)(nil)).Elem()
if t.Implements(linker) {
*snips = append(*snips, &snippets.CanFollow{Path: pth})
}
// Visit the children.
switch t.Kind() {
case reflect.Struct:
addCanFollowStruct(pth, t, snips, seen, false)
case reflect.Array, reflect.Slice:
addCanFollow(snippets.Elem(pth), t.Elem(), snips, seen)
case reflect.Map:
addCanFollow(snippets.Key(pth), t.Key(), snips, seen)
addCanFollow(snippets.Elem(pth), t.Elem(), snips, seen)
}
}
// addCanFollowStruct adds CanFollow snippets to a struct and all its children.
func addCanFollowStruct(path snippets.Pathway, t reflect.Type, snips *[]binary.Object, seen seenStack, fixCase bool) {
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if len(f.PkgPath) != 0 {
// Non-empty package path means this field is unexported, ignore.
continue
}
if i == 0 && f.Anonymous && f.Type.Kind() == reflect.Struct &&
f.Type.NumField() == 0 {
// Look for tags on the binary.Generate field
typeName := f.Tag.Get("display")
globals := f.Tag.Get("globals")
if globals != "" {
if fixCase {
// Fix case only applies to parameters, not globals
return
}
if globals != snippets.GlobalsTypename {
panic(fmt.Errorf("Type %v is tagged globals:\"%s\". Does not match with \"%s\"", t, globals, snippets.GlobalsTypename))
}
typeName = globals
}
if path == nil {
if typeName == "" {
return
}
path = snippets.Relative(typeName)
}
continue
}
addCanFollow(field(path, f.Name, fixCase), f.Type, snips, seen)
}
}
func addCanFollowAtom(t reflect.Type, snips *[]binary.Object) {
addCanFollowStruct(nil, t, snips, seenStack{t}, true)
}
// AddCanFollowState add CanFollow snippets for the global API state object.
func AddCanFollowState(t reflect.Type, snips *[]binary.Object) {
addCanFollowStruct(nil, t, snips, seenStack{t}, false)
}
func lowerCaseFirstCharacter(str string) string {
a := []rune(str)
a[0] = unicode.ToLower(a[0])
return string(a)
}
func field(path snippets.Pathway, name string, fixcase bool) snippets.Pathway {
if fixcase {
// See b/27585620
name = lowerCaseFirstCharacter(name)
}
return snippets.Field(path, name)
}
func init() {
linker := reflect.TypeOf((*path.Linker)(nil)).Elem()
linkableMetaFactory := func(t reflect.Type, e *binary.Entity) {
defer func() {
if r := recover(); r != nil {
panic(fmt.Errorf("panic during %v: %v", t.Name, r))
}
}()
// Add CanFollow snippets for all the atoms
if t.Implements(linker) {
// Ideally we would use atom.Atom here, but that creates a cycle.
return
}
// Add CanFollow snippets to the parameters of the atom
addCanFollowAtom(t, &e.Metadata)
}
registry.Factories.AddMetaFactory(linkableMetaFactory)
}