text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <title>TopCoder SRM 708 - 250: BalancedStrings</title>

    <link type="image/x-icon" rel="shortcut icon" href="http://www.topcoder.com/i/favicon.ico"/>

        SRM 708
        1
        250
        BalancedStrings

Problem Statement

            <intro escaped="1">
The instability of a string is the number of pairs of consecutive characters that are distinct.
For example, the instability of "aafcccca" is 3 and the instability of "aaaaaaaaa" is 0.

The total instability of a sequence of strings is the sum of instabilities of the individual strings.

The similarity between two strings s1 and s2 is the number of pairs of equal characters.
In other words, it is the number of pairs of indices (i,j) such that s1[i] = s2[j].
For example, the similarity between "aaaabc" and "xba" is 5 and the similarity between "xxx" and "xxxx" is 12.

The total similarity of a sequence of M strings is computed as follows:
For each of the M*(M-1)/2 possible pairs of strings you compute the similarity between them, and then you sum those M*(M-1)/2 numbers.

Limak needs some balance in his life.
He asked you to find a sequence of exactly N strings satisfying the following conditions:

The total instability of the sequence is equal to the total similarity of the sequence.

Each string contains between 1 and 100 characters, inclusive.

Each string contains only lowercase English letters ('a' - 'z').

For the given constraints a solution always exists.
If there are many solutions, you may return any one of them.
</intro>

Definition

                    Class
                    BalancedStrings

                    Method
                    findAny

                    Parameters

                        int

                    Returns

                        vector<string>

                    Method signature

                        vector<string> findAny(int N)

(be sure your method is public)

Limits

                    Time limit (s)
                    2.000

                    Memory limit (MB)
                    256

Constraints

<user-constraint escaped="1">N will be between 1 and 100, inclusive.</user-constraint>

Test cases

input

                                N

                                    3

output

                        Returns

                            { "eertree", "topcoder", "arena" }

note

We have:

instability("eertree") = 4

instability("topcoder") = 7

instability("arena") = 4

Thus, the total instability is 4 + 7 + 4 = 15.

We also have:

similarity("eertree","topcoder") = 7

similarity("eertree","arena") = 6

similarity("topcoder","arena") = 2

Thus, the total similarity is 7 + 6 + 2 = 15, which is the same as the total instability.

input

                                N

                                    4

output

                        Returns

                            { "hello", "little", "polar", "bear" }

note

In the provided answer, the total instability and the total similarity are both equal to 14.

input

                                N

                                    5

output

                        Returns

                            { "abbbbbbbbbbbbczaaaaaao", "c", "zz", "c", "xxxyyyzvvv" }

note

Note that the returned sequence may contain multiple copies of the same string.
Each of those is counted separately.

input

                                N

                                    1

output

                        Returns

                            { "kkkkkkkkkkkkkkkkkkk" }

input

                                N

                                    10

output

                        Returns

                            { "asdflkjhsdfsfffkdhjfdlshlfds", "pudelek", "xo", "xo", "mnbvmnmbbr", "plox", "qqwwrrttyyy", "you", "are", "awesome" }

    This problem statement is the exclusive and proprietary property of TopCoder, Inc. Any unauthorized use or reproduction of this information without the prior written consent of TopCoder, Inc. is strictly prohibited. (c)2003, TopCoder, Inc. All rights reserved.


text:
package rocks.athrow.android_udacity_reviews.data;

import rocks.athrow.android_udacity_reviews.util.Utilities;

/**
 * SummaryObject
 * A class to store data objects for the ReportsFragment
 * Types: Reviews or Project
 * The Reviews object holds the grand total for the report
 * The Project object holds the sub total for a reports_project
 * Created by josel on 8/20/2016.
 */
public class SummaryObject {
    // The type of SummaryObject (reviews or project)
    String type;
    // Reviews Summary fields
    int reviewsCount;
    Number reviewsRevenue;
    Number elapsedTime;
    // Project Summary fields
    String projectName;
    int projectCount;
    Number projectRevenue;
    long projectHours;

    /**
     * SummaryObject
     *
     * @param type           a reviews type SummaryObject
     * @param reviewsCount   the total count of reviews
     * @param reviewsRevenue the total revenue for the reviews
     * @param elapsedTime    the total time between assignment and completion, in millis
     */
    public SummaryObject(String type, int reviewsCount, Number reviewsRevenue, Number elapsedTime) {
        this.type = type;
        this.reviewsCount = reviewsCount;
        this.reviewsRevenue = reviewsRevenue;
        this.elapsedTime = elapsedTime;
    }

    /**
     * getReviewsCount
     *
     * @return the number of reviews in a String for display purposes
     */
    public String getReviewsCount() {
        return Integer.toString(reviewsCount);
    }

    /**
     * getReviewsRevenue
     *
     * @return the total revenue formatted in currency for display purposes
     */
    public String getReviewsRevenue() {
        return Utilities.formatCurrency(reviewsRevenue.doubleValue());
    }

    /**
     * getElapsedTime
     *
     * @return the total time spent from assignments to completions in hh:mm format
     */
    public String getElapsedTime() {
        return Utilities.millisecondsToHours(elapsedTime.longValue());
    }

    /**
     * SummaryObject
     *
     * @param type           a project type SummaryObject
     * @param projectName    the name of the project
     * @param projectCount   the total count for the project
     * @param projectRevenue the total revenue for the project
     * @param projectHours   the total hours for the project
     */
    public SummaryObject(String type, String projectName, int projectCount,
                         Number projectRevenue, long projectHours) {
        this.type = type;
        this.projectName = projectName;
        this.projectCount = projectCount;
        this.projectRevenue = projectRevenue;
        this.projectHours = projectHours;
    }

    /**
     * getProjectName
     *
     * @return the name of the project
     */
    public String getProjectName() {
        return this.projectName;
    }

    /**
     * getProjectCount
     *
     * @return the total count of project in String for display purposes
     */
    public String getProjectCount() {
        return Integer.toString(projectCount);
    }

    /**
     * getProjectRevenue
     *
     * @return the total revenue formatted in currency for display purposes
     */
    public String getProjectRevenue() {
        return Utilities.formatCurrency(projectRevenue.doubleValue());
    }

    /**
     * getpPojectHours
     *
     * @return the total time spent from assignments to completions in hh:mm format
     */
    public String getProjectHours() {
        return Utilities.millisecondsToHours(this.projectHours);
    }
}

redpajama_set_name: RedPajamaGithub


text:
layout: post
title: Not much
date: 2007-10-23T05:53:00.000Z
author: Marcus Hammarberg
tags:
  - Marcus private
  - Life of a consultant
  - Scrum
modified_time: 2007-10-23T06:05:11.333Z
blogger_id: tag:blogger.com,1999:blog-36533086.post-4313070354885416499
blogger_orig_url: http://www.marcusoft.net/2007/10/not-much.html
---

I've just entered a period of my project at work that is learning me why
SCRUM "iterations" are called sprints. After we have sprinted for seven
sprints in a row, with only a weekend in between each we have not
started a period of bug-cleaning.

And it feels so slow (this is the second week, mind you). Nothing that
pushes you, no goal in sight and just bugs coming in and being fixed.
Booooring!

But hey - if you're not bored the fun will not
feel as fun. I guess.

OK - in my private life there is loads of things to do right now so i
might be a blessing in disguise. I also try to do some reading up on new
technolgies - first in line is
[WCF](http://www.google.se/search?hl=sv&q=wcf+introduction&meta=) and
[LINQ](http://www.google.se/search?hl=sv&q=linq+introduction&meta=)


text: server, phone, and kiosk-side code for urban fitness public "kiosks"


text:
using namespace llvm;

// Return true if Expr is in the range [MinValue, MaxValue].
static bool inRange(const MCExpr *Expr, int64_t MinValue, int64_t MaxValue) {
  if (auto *CE = dyn_cast<MCConstantExpr>(Expr)) {
    int64_t Value = CE->getValue();
    return Value >= MinValue && Value <= MaxValue;
  }
  return false;
}

namespace {
enum RegisterKind {
  GR32Reg,
  GRH32Reg,
  GR64Reg,
  GR128Reg,
  ADDR32Reg,
  ADDR64Reg,
  FP32Reg,
  FP64Reg,
  FP128Reg,
  VR32Reg,
  VR64Reg,
  VR128Reg
};

enum MemoryKind {
  BDMem,
  BDXMem,
  BDLMem,
  BDVMem
};

class SystemZOperand : public MCParsedAsmOperand {
public:
private:
  enum OperandKind {
    KindInvalid,
    KindToken,
    KindReg,
    KindAccessReg,
    KindImm,
    KindImmTLS,
    KindMem
  };

  OperandKind Kind;
  SMLoc StartLoc, EndLoc;

  // A string of length Length, starting at Data.
  struct TokenOp {
    const char *Data;
    unsigned Length;
  };

  // LLVM register Num, which has kind Kind.  In some ways it might be
  // easier for this class to have a register bank (general, floating-point
  // or access) and a raw register number (0-15).  This would postpone the
  // interpretation of the operand to the add*() methods and avoid the need
  // for context-dependent parsing.  However, we do things the current way
  // because of the virtual getReg() method, which needs to distinguish
  // between (say) %r0 used as a single register and %r0 used as a pair.
  // Context-dependent parsing can also give us slightly better error
  // messages when invalid pairs like %r1 are used.
  struct RegOp {
    RegisterKind Kind;
    unsigned Num;
  };

  // Base + Disp + Index, where Base and Index are LLVM registers or 0.
  // MemKind says what type of memory this is and RegKind says what type
  // the base register has (ADDR32Reg or ADDR64Reg).  Length is the operand
  // length for D(L,B)-style operands, otherwise it is null.
  struct MemOp {
    unsigned Base : 12;
    unsigned Index : 12;
    unsigned MemKind : 4;
    unsigned RegKind : 4;
    const MCExpr *Disp;
    const MCExpr *Length;
  };

  // Imm is an immediate operand, and Sym is an optional TLS symbol
  // for use with a __tls_get_offset marker relocation.
  struct ImmTLSOp {
    const MCExpr *Imm;
    const MCExpr *Sym;
  };

  union {
    TokenOp Token;
    RegOp Reg;
    unsigned AccessReg;
    const MCExpr *Imm;
    ImmTLSOp ImmTLS;
    MemOp Mem;
  };

  void addExpr(MCInst &Inst, const MCExpr *Expr) const {
    // Add as immediates when possible.  Null MCExpr = 0.
    if (!Expr)
      Inst.addOperand(MCOperand::createImm(0));
    else if (auto *CE = dyn_cast<MCConstantExpr>(Expr))
      Inst.addOperand(MCOperand::createImm(CE->getValue()));
    else
      Inst.addOperand(MCOperand::createExpr(Expr));
  }

public:
  SystemZOperand(OperandKind kind, SMLoc startLoc, SMLoc endLoc)
      : Kind(kind), StartLoc(startLoc), EndLoc(endLoc) {}

  // Create particular kinds of operand.
  static std::unique_ptr<SystemZOperand> createInvalid(SMLoc StartLoc,
                                                       SMLoc EndLoc) {
    return make_unique<SystemZOperand>(KindInvalid, StartLoc, EndLoc);
  }
  static std::unique_ptr<SystemZOperand> createToken(StringRef Str, SMLoc Loc) {
    auto Op = make_unique<SystemZOperand>(KindToken, Loc, Loc);
    Op->Token.Data = Str.data();
    Op->Token.Length = Str.size();
    return Op;
  }
  static std::unique_ptr<SystemZOperand>
  createReg(RegisterKind Kind, unsigned Num, SMLoc StartLoc, SMLoc EndLoc) {
    auto Op = make_unique<SystemZOperand>(KindReg, StartLoc, EndLoc);
    Op->Reg.Kind = Kind;
    Op->Reg.Num = Num;
    return Op;
  }
  static std::unique_ptr<SystemZOperand>
  createAccessReg(unsigned Num, SMLoc StartLoc, SMLoc EndLoc) {
    auto Op = make_unique<SystemZOperand>(KindAccessReg, StartLoc, EndLoc);
    Op->AccessReg = Num;
    return Op;
  }
  static std::unique_ptr<SystemZOperand>
  createImm(const MCExpr *Expr, SMLoc StartLoc, SMLoc EndLoc) {
    auto Op = make_unique<SystemZOperand>(KindImm, StartLoc, EndLoc);
    Op->Imm = Expr;
    return Op;
  }
  static std::unique_ptr<SystemZOperand>
  createMem(MemoryKind MemKind, RegisterKind RegKind, unsigned Base,
            const MCExpr *Disp, unsigned Index, const MCExpr *Length,
            SMLoc StartLoc, SMLoc EndLoc) {
    auto Op = make_unique<SystemZOperand>(KindMem, StartLoc, EndLoc);
    Op->Mem.MemKind = MemKind;
    Op->Mem.RegKind = RegKind;
    Op->Mem.Base = Base;
    Op->Mem.Index = Index;
    Op->Mem.Disp = Disp;
    Op->Mem.Length = Length;
    return Op;
  }
  static std::unique_ptr<SystemZOperand>
  createImmTLS(const MCExpr *Imm, const MCExpr *Sym,
               SMLoc StartLoc, SMLoc EndLoc) {
    auto Op = make_unique<SystemZOperand>(KindImmTLS, StartLoc, EndLoc);
    Op->ImmTLS.Imm = Imm;
    Op->ImmTLS.Sym = Sym;
    return Op;
  }

  // Token operands
  bool isToken() const override {
    return Kind == KindToken;
  }
  StringRef getToken() const {
    assert(Kind == KindToken && "Not a token");
    return StringRef(Token.Data, Token.Length);
  }

  // Register operands.
  bool isReg() const override {
    return Kind == KindReg;
  }
  bool isReg(RegisterKind RegKind) const {
    return Kind == KindReg && Reg.Kind == RegKind;
  }
  unsigned getReg() const override {
    assert(Kind == KindReg && "Not a register");
    return Reg.Num;
  }

  // Access register operands.  Access registers aren't exposed to LLVM
  // as registers.
  bool isAccessReg() const {
    return Kind == KindAccessReg;
  }

  // Immediate operands.
  bool isImm() const override {
    return Kind == KindImm;
  }
  bool isImm(int64_t MinValue, int64_t MaxValue) const {
    return Kind == KindImm && inRange(Imm, MinValue, MaxValue);
  }
  const MCExpr *getImm() const {
    assert(Kind == KindImm && "Not an immediate");
    return Imm;
  }

  // Immediate operands with optional TLS symbol.
  bool isImmTLS() const {
    return Kind == KindImmTLS;
  }

  // Memory operands.
  bool isMem() const override {
    return Kind == KindMem;
  }
  bool isMem(MemoryKind MemKind) const {
    return (Kind == KindMem &&
            (Mem.MemKind == MemKind ||
             // A BDMem can be treated as a BDXMem in which the index
             // register field is 0.
             (Mem.MemKind == BDMem && MemKind == BDXMem)));
  }
  bool isMem(MemoryKind MemKind, RegisterKind RegKind) const {
    return isMem(MemKind) && Mem.RegKind == RegKind;
  }
  bool isMemDisp12(MemoryKind MemKind, RegisterKind RegKind) const {
    return isMem(MemKind, RegKind) && inRange(Mem.Disp, 0, 0xfff);
  }
  bool isMemDisp20(MemoryKind MemKind, RegisterKind RegKind) const {
    return isMem(MemKind, RegKind) && inRange(Mem.Disp, -524288, 524287);
  }
  bool isMemDisp12Len8(RegisterKind RegKind) const {
    return isMemDisp12(BDLMem, RegKind) && inRange(Mem.Length, 1, 0x100);
  }
  void addBDVAddrOperands(MCInst &Inst, unsigned N) const {
    assert(N == 3 && "Invalid number of operands");
    assert(isMem(BDVMem) && "Invalid operand type");
    Inst.addOperand(MCOperand::createReg(Mem.Base));
    addExpr(Inst, Mem.Disp);
    Inst.addOperand(MCOperand::createReg(Mem.Index));
  }

  // Override MCParsedAsmOperand.
  SMLoc getStartLoc() const override { return StartLoc; }
  SMLoc getEndLoc() const override { return EndLoc; }
  void print(raw_ostream &OS) const override;

  // Used by the TableGen code to add particular types of operand
  // to an instruction.
  void addRegOperands(MCInst &Inst, unsigned N) const {
    assert(N == 1 && "Invalid number of operands");
    Inst.addOperand(MCOperand::createReg(getReg()));
  }
  void addAccessRegOperands(MCInst &Inst, unsigned N) const {
    assert(N == 1 && "Invalid number of operands");
    assert(Kind == KindAccessReg && "Invalid operand type");
    Inst.addOperand(MCOperand::createImm(AccessReg));
  }
  void addImmOperands(MCInst &Inst, unsigned N) const {
    assert(N == 1 && "Invalid number of operands");
    addExpr(Inst, getImm());
  }
  void addBDAddrOperands(MCInst &Inst, unsigned N) const {
    assert(N == 2 && "Invalid number of operands");
    assert(isMem(BDMem) && "Invalid operand type");
    Inst.addOperand(MCOperand::createReg(Mem.Base));
    addExpr(Inst, Mem.Disp);
  }
  void addBDXAddrOperands(MCInst &Inst, unsigned N) const {
    assert(N == 3 && "Invalid number of operands");
    assert(isMem(BDXMem) && "Invalid operand type");
    Inst.addOperand(MCOperand::createReg(Mem.Base));
    addExpr(Inst, Mem.Disp);
    Inst.addOperand(MCOperand::createReg(Mem.Index));
  }
  void addBDLAddrOperands(MCInst &Inst, unsigned N) const {
    assert(N == 3 && "Invalid number of operands");
    assert(isMem(BDLMem) && "Invalid operand type");
    Inst.addOperand(MCOperand::createReg(Mem.Base));
    addExpr(Inst, Mem.Disp);
    addExpr(Inst, Mem.Length);
  }
  void addImmTLSOperands(MCInst &Inst, unsigned N) const {
    assert(N == 2 && "Invalid number of operands");
    assert(Kind == KindImmTLS && "Invalid operand type");
    addExpr(Inst, ImmTLS.Imm);
    if (ImmTLS.Sym)
      addExpr(Inst, ImmTLS.Sym);
  }

  // Used by the TableGen code to check for particular operand types.
  bool isGR32() const { return isReg(GR32Reg); }
  bool isGRH32() const { return isReg(GRH32Reg); }
  bool isGRX32() const { return false; }
  bool isGR64() const { return isReg(GR64Reg); }
  bool isGR128() const { return isReg(GR128Reg); }
  bool isADDR32() const { return isReg(ADDR32Reg); }
  bool isADDR64() const { return isReg(ADDR64Reg); }
  bool isADDR128() const { return false; }
  bool isFP32() const { return isReg(FP32Reg); }
  bool isFP64() const { return isReg(FP64Reg); }
  bool isFP128() const { return isReg(FP128Reg); }
  bool isVR32() const { return isReg(VR32Reg); }
  bool isVR64() const { return isReg(VR64Reg); }
  bool isVF128() const { return false; }
  bool isVR128() const { return isReg(VR128Reg); }
  bool isBDAddr32Disp12() const { return isMemDisp12(BDMem, ADDR32Reg); }
  bool isBDAddr32Disp20() const { return isMemDisp20(BDMem, ADDR32Reg); }
  bool isBDAddr64Disp12() const { return isMemDisp12(BDMem, ADDR64Reg); }
  bool isBDAddr64Disp20() const { return isMemDisp20(BDMem, ADDR64Reg); }
  bool isBDXAddr64Disp12() const { return isMemDisp12(BDXMem, ADDR64Reg); }
  bool isBDXAddr64Disp20() const { return isMemDisp20(BDXMem, ADDR64Reg); }
  bool isBDLAddr64Disp12Len8() const { return isMemDisp12Len8(ADDR64Reg); }
  bool isBDVAddr64Disp12() const { return isMemDisp12(BDVMem, ADDR64Reg); }
  bool isU1Imm() const { return isImm(0, 1); }
  bool isU2Imm() const { return isImm(0, 3); }
  bool isU3Imm() const { return isImm(0, 7); }
  bool isU4Imm() const { return isImm(0, 15); }
  bool isU6Imm() const { return isImm(0, 63); }
  bool isU8Imm() const { return isImm(0, 255); }
  bool isS8Imm() const { return isImm(-128, 127); }
  bool isU12Imm() const { return isImm(0, 4095); }
  bool isU16Imm() const { return isImm(0, 65535); }
  bool isS16Imm() const { return isImm(-32768, 32767); }
  bool isU32Imm() const { return isImm(0, (1LL << 32) - 1); }
  bool isS32Imm() const { return isImm(-(1LL << 31), (1LL << 31) - 1); }
};

class SystemZAsmParser : public MCTargetAsmParser {
#define GET_ASSEMBLER_HEADER
#include "SystemZGenAsmMatcher.inc"

private:
  MCSubtargetInfo &STI;
  MCAsmParser &Parser;
  enum RegisterGroup {
    RegGR,
    RegFP,
    RegV,
    RegAccess
  };
  struct Register {
    RegisterGroup Group;
    unsigned Num;
    SMLoc StartLoc, EndLoc;
  };

  bool parseRegister(Register &Reg);

  bool parseRegister(Register &Reg, RegisterGroup Group, const unsigned *Regs,
                     bool IsAddress = false);

  OperandMatchResultTy parseRegister(OperandVector &Operands,
                                     RegisterGroup Group, const unsigned *Regs,
                                     RegisterKind Kind);

  bool parseAddress(unsigned &Base, const MCExpr *&Disp,
                    unsigned &Index, bool &IsVector, const MCExpr *&Length,
                    const unsigned *Regs, RegisterKind RegKind);

  OperandMatchResultTy parseAddress(OperandVector &Operands,
                                    MemoryKind MemKind, const unsigned *Regs,
                                    RegisterKind RegKind);

  OperandMatchResultTy parsePCRel(OperandVector &Operands, int64_t MinVal,
                                  int64_t MaxVal, bool AllowTLS);

  bool parseOperand(OperandVector &Operands, StringRef Mnemonic);

public:
  SystemZAsmParser(MCSubtargetInfo &sti, MCAsmParser &parser,
                   const MCInstrInfo &MII,
                   const MCTargetOptions &Options)
      : MCTargetAsmParser(Options), STI(sti), Parser(parser) {
    MCAsmParserExtension::Initialize(Parser);

    // Initialize the set of available features.
    setAvailableFeatures(ComputeAvailableFeatures(STI.getFeatureBits()));
  }

  // Override MCTargetAsmParser.
  bool ParseDirective(AsmToken DirectiveID) override;
  bool ParseRegister(unsigned &RegNo, SMLoc &StartLoc, SMLoc &EndLoc) override;
  bool ParseInstruction(ParseInstructionInfo &Info, StringRef Name,
                        SMLoc NameLoc, OperandVector &Operands) override;
  bool MatchAndEmitInstruction(SMLoc IDLoc, unsigned &Opcode,
                               OperandVector &Operands, MCStreamer &Out,
                               uint64_t &ErrorInfo,
                               bool MatchingInlineAsm) override;

  // Used by the TableGen code to parse particular operand types.
  OperandMatchResultTy parseGR32(OperandVector &Operands) {
    return parseRegister(Operands, RegGR, SystemZMC::GR32Regs, GR32Reg);
  }
  OperandMatchResultTy parseGRH32(OperandVector &Operands) {
    return parseRegister(Operands, RegGR, SystemZMC::GRH32Regs, GRH32Reg);
  }
  OperandMatchResultTy parseGRX32(OperandVector &Operands) {
    llvm_unreachable("GRX32 should only be used for pseudo instructions");
  }
  OperandMatchResultTy parseGR64(OperandVector &Operands) {
    return parseRegister(Operands, RegGR, SystemZMC::GR64Regs, GR64Reg);
  }
  OperandMatchResultTy parseGR128(OperandVector &Operands) {
    return parseRegister(Operands, RegGR, SystemZMC::GR128Regs, GR128Reg);
  }
  OperandMatchResultTy parseADDR32(OperandVector &Operands) {
    return parseRegister(Operands, RegGR, SystemZMC::GR32Regs, ADDR32Reg);
  }
  OperandMatchResultTy parseADDR64(OperandVector &Operands) {
    return parseRegister(Operands, RegGR, SystemZMC::GR64Regs, ADDR64Reg);
  }
  OperandMatchResultTy parseADDR128(OperandVector &Operands) {
    llvm_unreachable("Shouldn't be used as an operand");
  }
  OperandMatchResultTy parseFP32(OperandVector &Operands) {
    return parseRegister(Operands, RegFP, SystemZMC::FP32Regs, FP32Reg);
  }
  OperandMatchResultTy parseFP64(OperandVector &Operands) {
    return parseRegister(Operands, RegFP, SystemZMC::FP64Regs, FP64Reg);
  }
  OperandMatchResultTy parseFP128(OperandVector &Operands) {
    return parseRegister(Operands, RegFP, SystemZMC::FP128Regs, FP128Reg);
  }
  OperandMatchResultTy parseVR32(OperandVector &Operands) {
    return parseRegister(Operands, RegV, SystemZMC::VR32Regs, VR32Reg);
  }
  OperandMatchResultTy parseVR64(OperandVector &Operands) {
    return parseRegister(Operands, RegV, SystemZMC::VR64Regs, VR64Reg);
  }
  OperandMatchResultTy parseVF128(OperandVector &Operands) {
    llvm_unreachable("Shouldn't be used as an operand");
  }
  OperandMatchResultTy parseVR128(OperandVector &Operands) {
    return parseRegister(Operands, RegV, SystemZMC::VR128Regs, VR128Reg);
  }
  OperandMatchResultTy parseBDAddr32(OperandVector &Operands) {
    return parseAddress(Operands, BDMem, SystemZMC::GR32Regs, ADDR32Reg);
  }
  OperandMatchResultTy parseBDAddr64(OperandVector &Operands) {
    return parseAddress(Operands, BDMem, SystemZMC::GR64Regs, ADDR64Reg);
  }
  OperandMatchResultTy parseBDXAddr64(OperandVector &Operands) {
    return parseAddress(Operands, BDXMem, SystemZMC::GR64Regs, ADDR64Reg);
  }
  OperandMatchResultTy parseBDLAddr64(OperandVector &Operands) {
    return parseAddress(Operands, BDLMem, SystemZMC::GR64Regs, ADDR64Reg);
  }
  OperandMatchResultTy parseBDVAddr64(OperandVector &Operands) {
    return parseAddress(Operands, BDVMem, SystemZMC::GR64Regs, ADDR64Reg);
  }
  OperandMatchResultTy parseAccessReg(OperandVector &Operands);
  OperandMatchResultTy parsePCRel16(OperandVector &Operands) {
    return parsePCRel(Operands, -(1LL << 16), (1LL << 16) - 1, false);
  }
  OperandMatchResultTy parsePCRel32(OperandVector &Operands) {
    return parsePCRel(Operands, -(1LL << 32), (1LL << 32) - 1, false);
  }
  OperandMatchResultTy parsePCRelTLS16(OperandVector &Operands) {
    return parsePCRel(Operands, -(1LL << 16), (1LL << 16) - 1, true);
  }
  OperandMatchResultTy parsePCRelTLS32(OperandVector &Operands) {
    return parsePCRel(Operands, -(1LL << 32), (1LL << 32) - 1, true);
  }
};
} // end anonymous namespace

#define GET_REGISTER_MATCHER
#define GET_SUBTARGET_FEATURE_NAME
#define GET_MATCHER_IMPLEMENTATION
#include "SystemZGenAsmMatcher.inc"

void SystemZOperand::print(raw_ostream &OS) const {
  llvm_unreachable("Not implemented");
}

// Parse one register of the form %<prefix><number>.
bool SystemZAsmParser::parseRegister(Register &Reg) {
  Reg.StartLoc = Parser.getTok().getLoc();

  // Eat the % prefix.
  if (Parser.getTok().isNot(AsmToken::Percent))
    return Error(Parser.getTok().getLoc(), "register expected");
  Parser.Lex();

  // Expect a register name.
  if (Parser.getTok().isNot(AsmToken::Identifier))
    return Error(Reg.StartLoc, "invalid register");

  // Check that there's a prefix.
  StringRef Name = Parser.getTok().getString();
  if (Name.size() < 2)
    return Error(Reg.StartLoc, "invalid register");
  char Prefix = Name[0];

  // Treat the rest of the register name as a register number.
  if (Name.substr(1).getAsInteger(10, Reg.Num))
    return Error(Reg.StartLoc, "invalid register");

  // Look for valid combinations of prefix and number.
  if (Prefix == 'r' && Reg.Num < 16)
    Reg.Group = RegGR;
  else if (Prefix == 'f' && Reg.Num < 16)
    Reg.Group = RegFP;
  else if (Prefix == 'v' && Reg.Num < 32)
    Reg.Group = RegV;
  else if (Prefix == 'a' && Reg.Num < 16)
    Reg.Group = RegAccess;
  else
    return Error(Reg.StartLoc, "invalid register");

  Reg.EndLoc = Parser.getTok().getLoc();
  Parser.Lex();
  return false;
}

// Parse a register of group Group.  If Regs is nonnull, use it to map
// the raw register number to LLVM numbering, with zero entries indicating
// an invalid register.  IsAddress says whether the register appears in an
// address context.
bool SystemZAsmParser::parseRegister(Register &Reg, RegisterGroup Group,
                                     const unsigned *Regs, bool IsAddress) {
  if (parseRegister(Reg))
    return true;
  if (Reg.Group != Group)
    return Error(Reg.StartLoc, "invalid operand for instruction");
  if (Regs && Regs[Reg.Num] == 0)
    return Error(Reg.StartLoc, "invalid register pair");
  if (Reg.Num == 0 && IsAddress)
    return Error(Reg.StartLoc, "%r0 used in an address");
  if (Regs)
    Reg.Num = Regs[Reg.Num];
  return false;
}

// Parse a register and add it to Operands.  The other arguments are as above.
SystemZAsmParser::OperandMatchResultTy
SystemZAsmParser::parseRegister(OperandVector &Operands, RegisterGroup Group,
                                const unsigned *Regs, RegisterKind Kind) {
  if (Parser.getTok().isNot(AsmToken::Percent))
    return MatchOperand_NoMatch;

  Register Reg;
  bool IsAddress = (Kind == ADDR32Reg || Kind == ADDR64Reg);
  if (parseRegister(Reg, Group, Regs, IsAddress))
    return MatchOperand_ParseFail;

  Operands.push_back(SystemZOperand::createReg(Kind, Reg.Num,
                                               Reg.StartLoc, Reg.EndLoc));
  return MatchOperand_Success;
}

// Parse a memory operand into Base, Disp, Index and Length.
// Regs maps asm register numbers to LLVM register numbers and RegKind
// says what kind of address register we're using (ADDR32Reg or ADDR64Reg).
bool SystemZAsmParser::parseAddress(unsigned &Base, const MCExpr *&Disp,
                                    unsigned &Index, bool &IsVector,
                                    const MCExpr *&Length, const unsigned *Regs,
                                    RegisterKind RegKind) {
  // Parse the displacement, which must always be present.
  if (getParser().parseExpression(Disp))
    return true;

  // Parse the optional base and index.
  Index = 0;
  Base = 0;
  IsVector = false;
  Length = nullptr;
  if (getLexer().is(AsmToken::LParen)) {
    Parser.Lex();

    if (getLexer().is(AsmToken::Percent)) {
      // Parse the first register and decide whether it's a base or an index.
      Register Reg;
      if (parseRegister(Reg))
        return true;
      if (Reg.Group == RegV) {
        // A vector index register.  The base register is optional.
        IsVector = true;
        Index = SystemZMC::VR128Regs[Reg.Num];
      } else if (Reg.Group == RegGR) {
        if (Reg.Num == 0)
          return Error(Reg.StartLoc, "%r0 used in an address");
        // If the are two registers, the first one is the index and the
        // second is the base.
        if (getLexer().is(AsmToken::Comma))
          Index = Regs[Reg.Num];
        else
          Base = Regs[Reg.Num];
      } else
        return Error(Reg.StartLoc, "invalid address register");
    } else {
      // Parse the length.
      if (getParser().parseExpression(Length))
        return true;
    }

    // Check whether there's a second register.  It's the base if so.
    if (getLexer().is(AsmToken::Comma)) {
      Parser.Lex();
      Register Reg;
      if (parseRegister(Reg, RegGR, Regs, RegKind))
        return true;
      Base = Reg.Num;
    }

    // Consume the closing bracket.
    if (getLexer().isNot(AsmToken::RParen))
      return Error(Parser.getTok().getLoc(), "unexpected token in address");
    Parser.Lex();
  }
  return false;
}

// Parse a memory operand and add it to Operands.  The other arguments
// are as above.
SystemZAsmParser::OperandMatchResultTy
SystemZAsmParser::parseAddress(OperandVector &Operands, MemoryKind MemKind,
                               const unsigned *Regs, RegisterKind RegKind) {
  SMLoc StartLoc = Parser.getTok().getLoc();
  unsigned Base, Index;
  bool IsVector;
  const MCExpr *Disp;
  const MCExpr *Length;
  if (parseAddress(Base, Disp, Index, IsVector, Length, Regs, RegKind))
    return MatchOperand_ParseFail;

  if (IsVector && MemKind != BDVMem) {
    Error(StartLoc, "invalid use of vector addressing");
    return MatchOperand_ParseFail;
  }

  if (!IsVector && MemKind == BDVMem) {
    Error(StartLoc, "vector index required in address");
    return MatchOperand_ParseFail;
  }

  if (Index && MemKind != BDXMem && MemKind != BDVMem) {
    Error(StartLoc, "invalid use of indexed addressing");
    return MatchOperand_ParseFail;
  }

  if (Length && MemKind != BDLMem) {
    Error(StartLoc, "invalid use of length addressing");
    return MatchOperand_ParseFail;
  }

  if (!Length && MemKind == BDLMem) {
    Error(StartLoc, "missing length in address");
    return MatchOperand_ParseFail;
  }

  SMLoc EndLoc =
    SMLoc::getFromPointer(Parser.getTok().getLoc().getPointer() - 1);
  Operands.push_back(SystemZOperand::createMem(MemKind, RegKind, Base, Disp,
                                               Index, Length, StartLoc,
                                               EndLoc));
  return MatchOperand_Success;
}

bool SystemZAsmParser::ParseDirective(AsmToken DirectiveID) {
  return true;
}

bool SystemZAsmParser::ParseRegister(unsigned &RegNo, SMLoc &StartLoc,
                                     SMLoc &EndLoc) {
  Register Reg;
  if (parseRegister(Reg))
    return true;
  if (Reg.Group == RegGR)
    RegNo = SystemZMC::GR64Regs[Reg.Num];
  else if (Reg.Group == RegFP)
    RegNo = SystemZMC::FP64Regs[Reg.Num];
  else if (Reg.Group == RegV)
    RegNo = SystemZMC::VR128Regs[Reg.Num];
  else
    // FIXME: Access registers aren't modelled as LLVM registers yet.
    return Error(Reg.StartLoc, "invalid operand for instruction");
  StartLoc = Reg.StartLoc;
  EndLoc = Reg.EndLoc;
  return false;
}

bool SystemZAsmParser::ParseInstruction(ParseInstructionInfo &Info,
                                        StringRef Name, SMLoc NameLoc,
                                        OperandVector &Operands) {
  Operands.push_back(SystemZOperand::createToken(Name, NameLoc));

  // Read the remaining operands.
  if (getLexer().isNot(AsmToken::EndOfStatement)) {
    // Read the first operand.
    if (parseOperand(Operands, Name)) {
      Parser.eatToEndOfStatement();
      return true;
    }

    // Read any subsequent operands.
    while (getLexer().is(AsmToken::Comma)) {
      Parser.Lex();
      if (parseOperand(Operands, Name)) {
        Parser.eatToEndOfStatement();
        return true;
      }
    }
    if (getLexer().isNot(AsmToken::EndOfStatement)) {
      SMLoc Loc = getLexer().getLoc();
      Parser.eatToEndOfStatement();
      return Error(Loc, "unexpected token in argument list");
    }
  }

  // Consume the EndOfStatement.
  Parser.Lex();
  return false;
}

bool SystemZAsmParser::parseOperand(OperandVector &Operands,
                                    StringRef Mnemonic) {
  // Check if the current operand has a custom associated parser, if so, try to
  // custom parse the operand, or fallback to the general approach.
  OperandMatchResultTy ResTy = MatchOperandParserImpl(Operands, Mnemonic);
  if (ResTy == MatchOperand_Success)
    return false;

  // If there wasn't a custom match, try the generic matcher below. Otherwise,
  // there was a match, but an error occurred, in which case, just return that
  // the operand parsing failed.
  if (ResTy == MatchOperand_ParseFail)
    return true;

  // Check for a register.  All real register operands should have used
  // a context-dependent parse routine, which gives the required register
  // class.  The code is here to mop up other cases, like those where
  // the instruction isn't recognized.
  if (Parser.getTok().is(AsmToken::Percent)) {
    Register Reg;
    if (parseRegister(Reg))
      return true;
    Operands.push_back(SystemZOperand::createInvalid(Reg.StartLoc, Reg.EndLoc));
    return false;
  }

  // The only other type of operand is an immediate or address.  As above,
  // real address operands should have used a context-dependent parse routine,
  // so we treat any plain expression as an immediate.
  SMLoc StartLoc = Parser.getTok().getLoc();
  unsigned Base, Index;
  bool IsVector;
  const MCExpr *Expr, *Length;
  if (parseAddress(Base, Expr, Index, IsVector, Length, SystemZMC::GR64Regs,
                   ADDR64Reg))
    return true;

  SMLoc EndLoc =
    SMLoc::getFromPointer(Parser.getTok().getLoc().getPointer() - 1);
  if (Base || Index || Length)
    Operands.push_back(SystemZOperand::createInvalid(StartLoc, EndLoc));
  else
    Operands.push_back(SystemZOperand::createImm(Expr, StartLoc, EndLoc));
  return false;
}

bool SystemZAsmParser::MatchAndEmitInstruction(SMLoc IDLoc, unsigned &Opcode,
                                               OperandVector &Operands,
                                               MCStreamer &Out,
                                               uint64_t &ErrorInfo,
                                               bool MatchingInlineAsm) {
  MCInst Inst;
  unsigned MatchResult;

  MatchResult = MatchInstructionImpl(Operands, Inst, ErrorInfo,
                                     MatchingInlineAsm);
  switch (MatchResult) {
  case Match_Success:
    Inst.setLoc(IDLoc);
    Out.EmitInstruction(Inst, STI);
    return false;

  case Match_MissingFeature: {
    assert(ErrorInfo && "Unknown missing feature!");
    // Special case the error message for the very common case where only
    // a single subtarget feature is missing
    std::string Msg = "instruction requires:";
    uint64_t Mask = 1;
    for (unsigned I = 0; I < sizeof(ErrorInfo) * 8 - 1; ++I) {
      if (ErrorInfo & Mask) {
        Msg += " ";
        Msg += getSubtargetFeatureName(ErrorInfo & Mask);
      }
      Mask <<= 1;
    }
    return Error(IDLoc, Msg);
  }

  case Match_InvalidOperand: {
    SMLoc ErrorLoc = IDLoc;
    if (ErrorInfo != ~0ULL) {
      if (ErrorInfo >= Operands.size())
        return Error(IDLoc, "too few operands for instruction");

      ErrorLoc = ((SystemZOperand &)*Operands[ErrorInfo]).getStartLoc();
      if (ErrorLoc == SMLoc())
        ErrorLoc = IDLoc;
    }
    return Error(ErrorLoc, "invalid operand for instruction");
  }

  case Match_MnemonicFail:
    return Error(IDLoc, "invalid instruction");
  }

  llvm_unreachable("Unexpected match type");
}

SystemZAsmParser::OperandMatchResultTy
SystemZAsmParser::parseAccessReg(OperandVector &Operands) {
  if (Parser.getTok().isNot(AsmToken::Percent))
    return MatchOperand_NoMatch;

  Register Reg;
  if (parseRegister(Reg, RegAccess, nullptr))
    return MatchOperand_ParseFail;

  Operands.push_back(SystemZOperand::createAccessReg(Reg.Num,
                                                     Reg.StartLoc,
                                                     Reg.EndLoc));
  return MatchOperand_Success;
}

SystemZAsmParser::OperandMatchResultTy
SystemZAsmParser::parsePCRel(OperandVector &Operands, int64_t MinVal,
                             int64_t MaxVal, bool AllowTLS) {
  MCContext &Ctx = getContext();
  MCStreamer &Out = getStreamer();
  const MCExpr *Expr;
  SMLoc StartLoc = Parser.getTok().getLoc();
  if (getParser().parseExpression(Expr))
    return MatchOperand_NoMatch;

  // For consistency with the GNU assembler, treat immediates as offsets
  // from ".".
  if (auto *CE = dyn_cast<MCConstantExpr>(Expr)) {
    int64_t Value = CE->getValue();
    if ((Value & 1) || Value < MinVal || Value > MaxVal) {
      Error(StartLoc, "offset out of range");
      return MatchOperand_ParseFail;
    }
    MCSymbol *Sym = Ctx.createTempSymbol();
    Out.EmitLabel(Sym);
    const MCExpr *Base = MCSymbolRefExpr::create(Sym, MCSymbolRefExpr::VK_None,
                                                 Ctx);
    Expr = Value == 0 ? Base : MCBinaryExpr::createAdd(Base, Expr, Ctx);
  }

  // Optionally match :tls_gdcall: or :tls_ldcall: followed by a TLS symbol.
  const MCExpr *Sym = nullptr;
  if (AllowTLS && getLexer().is(AsmToken::Colon)) {
    Parser.Lex();

    if (Parser.getTok().isNot(AsmToken::Identifier)) {
      Error(Parser.getTok().getLoc(), "unexpected token");
      return MatchOperand_ParseFail;
    }

    MCSymbolRefExpr::VariantKind Kind = MCSymbolRefExpr::VK_None;
    StringRef Name = Parser.getTok().getString();
    if (Name == "tls_gdcall")
      Kind = MCSymbolRefExpr::VK_TLSGD;
    else if (Name == "tls_ldcall")
      Kind = MCSymbolRefExpr::VK_TLSLDM;
    else {
      Error(Parser.getTok().getLoc(), "unknown TLS tag");
      return MatchOperand_ParseFail;
    }
    Parser.Lex();

    if (Parser.getTok().isNot(AsmToken::Colon)) {
      Error(Parser.getTok().getLoc(), "unexpected token");
      return MatchOperand_ParseFail;
    }
    Parser.Lex();

    if (Parser.getTok().isNot(AsmToken::Identifier)) {
      Error(Parser.getTok().getLoc(), "unexpected token");
      return MatchOperand_ParseFail;
    }

    StringRef Identifier = Parser.getTok().getString();
    Sym = MCSymbolRefExpr::create(Ctx.getOrCreateSymbol(Identifier),
                                  Kind, Ctx);
    Parser.Lex();
  }

  SMLoc EndLoc =
    SMLoc::getFromPointer(Parser.getTok().getLoc().getPointer() - 1);

  if (AllowTLS)
    Operands.push_back(SystemZOperand::createImmTLS(Expr, Sym,
                                                    StartLoc, EndLoc));
  else
    Operands.push_back(SystemZOperand::createImm(Expr, StartLoc, EndLoc));

  return MatchOperand_Success;
}

// Force static initialization.
extern "C" void LLVMInitializeSystemZAsmParser() {
  RegisterMCAsmParser<SystemZAsmParser> X(TheSystemZTarget);
}


text:
ACCEPTED

#### According to
Index Fungorum

#### Published in
Porosty (Lichenes). 3 (Lublin), Rodzina Lecanoraceae. Lecanora 17 (1996)

#### Original name
Lecanora supina Motyka

### Remarks
null


text:
define([
        '../Core/Cartesian3',
        '../Core/Color',
        '../Core/ComponentDatatype',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/IndexDatatype',
        '../Core/Matrix4',
        '../Core/PrimitiveType',
        '../Renderer/Buffer',
        '../Renderer/BufferUsage',
        '../Renderer/DrawCommand',
        '../Renderer/Pass',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Renderer/VertexArray',
        '../Shaders/ShadowVolumeFS',
        '../Shaders/VectorTileVS',
        './BlendingState',
        './Cesium3DTileFeature',
        './ClassificationType',
        './DepthFunction',
        './Expression',
        './StencilConstants',
        './StencilFunction',
        './StencilOperation',
        './Vector3DTileBatch'
    ], function(
        Cartesian3,
        Color,
        ComponentDatatype,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        IndexDatatype,
        Matrix4,
        PrimitiveType,
        Buffer,
        BufferUsage,
        DrawCommand,
        Pass,
        RenderState,
        ShaderProgram,
        ShaderSource,
        VertexArray,
        ShadowVolumeFS,
        VectorTileVS,
        BlendingState,
        Cesium3DTileFeature,
        ClassificationType,
        DepthFunction,
        Expression,
        StencilConstants,
        StencilFunction,
        StencilOperation,
        Vector3DTileBatch) {
    'use strict';

    /**
     * Creates a batch of classification meshes.
     *
     * @alias Vector3DTilePrimitive
     * @constructor
     *
     * @param {Object} options An object with following properties:
     * @param {Float32Array} options.positions The positions of the meshes.
     * @param {Uint16Array|Uint32Array} options.indices The indices of the triangulated meshes. The indices must be contiguous so that
     * the indices for mesh n are in [i, i + indexCounts[n]] where i = sum{indexCounts[0], indexCounts[n - 1]}.
     * @param {Uint32Array} options.indexCounts The number of indices for each mesh.
     * @param {Uint32Array} options.indexOffsets The offset into the index buffer for each mesh.
     * @param {Vector3DTileBatch[]} options.batchedIndices The index offset and count for each batch with the same color.
     * @param {Cartesian3} [options.center=Cartesian3.ZERO] The RTC center.
     * @param {Cesium3DTileBatchTable} options.batchTable The batch table for the tile containing the batched meshes.
     * @param {Uint16Array} options.batchIds The batch ids for each mesh.
     * @param {Uint16Array} options.vertexBatchIds The batch id for each vertex.
     * @param {BoundingSphere} options.boundingVolume The bounding volume for the entire batch of meshes.
     * @param {BoundingSphere[]} options.boundingVolumes The bounding volume for each mesh.
     * @param {ClassificationType} [options.classificationType] What this tile will classify.
     *
     * @private
     */
    function Vector3DTilePrimitive(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._batchTable = options.batchTable;
        this._batchIds = options.batchIds;

        // These arrays are released after VAO creation.
        this._positions = options.positions;
        this._vertexBatchIds = options.vertexBatchIds;

        // These arrays are kept for re-batching indices based on colors.
        // If WebGL 2 is supported, indices will be released and re-batching uses buffer-to-buffer copies.
        this._indices = options.indices;
        this._indexCounts = options.indexCounts;
        this._indexOffsets = options.indexOffsets;
        this._batchedIndices = options.batchedIndices;

        this._boundingVolume = options.boundingVolume;
        this._boundingVolumes = options.boundingVolumes;

        this._center = defaultValue(options.center, Cartesian3.ZERO);

        this._va = undefined;
        this._sp = undefined;
        this._spStencil = undefined;
        this._spPick = undefined;
        this._uniformMap = undefined;

        // Only used with WebGL 2 to ping-pong ibos after copy.
        this._vaSwap = undefined;

        this._rsStencilPreloadPass = undefined;
        this._rsStencilPreloadPass3DTiles = undefined;
        this._rsStencilDepthPass = undefined;
        this._rsStencilDepthPass3DTiles = undefined;
        this._rsColorPass = undefined;
        this._rsPickPass = undefined;
        this._rsWireframe = undefined;

        this._commands = [];
        this._commandsIgnoreShow = [];
        this._pickCommands = [];

        this._constantColor = Color.clone(Color.WHITE);
        this._highlightColor = this._constantColor;

        this._batchDirty = true;
        this._pickCommandsDirty = true;
        this._framesSinceLastRebatch = 0;

        this._updatingAllCommands = false;

        this._trianglesLength = this._indices.length / 3;
        this._geometryByteLength = this._indices.byteLength + this._positions.byteLength + this._vertexBatchIds.byteLength;

        /**
         * Draw the wireframe of the classification meshes.
         * @type {Boolean}
         * @default false
         */
        this.debugWireframe = false;
        this._debugWireframe = this.debugWireframe;
        this._wireframeDirty = false;

        /**
         * Forces a re-batch instead of waiting after a number of frames have been rendered. For testing only.
         * @type {Boolean}
         * @default false
         */
        this.forceRebatch = false;

        /**
         * What this tile will classify.
         * @type {ClassificationType}
         * @default ClassificationType.BOTH
         */
        this.classificationType = defaultValue(options.classificationType, ClassificationType.BOTH);

        // Hidden options
        this._vertexShaderSource = options._vertexShaderSource;
        this._fragmentShaderSource = options._fragmentShaderSource;
        this._attributeLocations = options._attributeLocations;
        this._uniformMap = options._uniformMap;
        this._pickId = options._pickId;
        this._modelMatrix = options._modelMatrix;
        this._boundingSphere = options._boundingSphere;

        this._batchIdLookUp = {};

        var length = this._batchIds.length;
        for (var i = 0; i < length; ++i) {
            var batchId = this._batchIds[i];
            this._batchIdLookUp[batchId] = i;
        }
    }

    defineProperties(Vector3DTilePrimitive.prototype, {
        /**
         * Gets the number of triangles.
         *
         * @memberof Vector3DTilePrimitive.prototype
         *
         * @type {Number}
         * @readonly
         */
        trianglesLength : {
            get : function() {
                return this._trianglesLength;
            }
        },

        /**
         * Gets the geometry memory in bytes.
         *
         * @memberof Vector3DTilePrimitive.prototype
         *
         * @type {Number}
         * @readonly
         */
        geometryByteLength : {
            get : function() {
                return this._geometryByteLength;
            }
        }
    });

    var defaultAttributeLocations = {
        position : 0,
        a_batchId : 1
    };

    function createVertexArray(primitive, context) {
        if (defined(primitive._va)) {
            return;
        }

        var positionBuffer = Buffer.createVertexBuffer({
            context : context,
            typedArray : primitive._positions,
            usage : BufferUsage.STATIC_DRAW
        });
        var idBuffer = Buffer.createVertexBuffer({
            context : context,
            typedArray : primitive._vertexBatchIds,
            usage : BufferUsage.STATIC_DRAW
        });
        var indexBuffer = Buffer.createIndexBuffer({
            context : context,
            typedArray : primitive._indices,
            usage : BufferUsage.DYNAMIC_DRAW,
            indexDatatype : (primitive._indices.BYTES_PER_ELEMENT === 2) ?  IndexDatatype.UNSIGNED_SHORT : IndexDatatype.UNSIGNED_INT
        });

        var vertexAttributes = [{
            index : 0,
            vertexBuffer : positionBuffer,
            componentDatatype : ComponentDatatype.fromTypedArray(primitive._positions),
            componentsPerAttribute : 3
        }, {
            index : 1,
            vertexBuffer : idBuffer,
            componentDatatype : ComponentDatatype.fromTypedArray(primitive._vertexBatchIds),
            componentsPerAttribute : 1
        }];

        primitive._va = new VertexArray({
            context : context,
            attributes : vertexAttributes,
            indexBuffer : indexBuffer
        });

        if (context.webgl2) {
            primitive._vaSwap = new VertexArray({
                context : context,
                attributes : vertexAttributes,
                indexBuffer : Buffer.createIndexBuffer({
                    context : context,
                    sizeInBytes : indexBuffer.sizeInBytes,
                    usage : BufferUsage.DYNAMIC_DRAW,
                    indexDatatype : indexBuffer.indexDatatype
                })
            });
        }

        primitive._batchedPositions = undefined;
        primitive._transferrableBatchIds = undefined;
        primitive._vertexBatchIds = undefined;
        primitive._verticesPromise = undefined;
    }

    function createShaders(primitive, context) {
        if (defined(primitive._sp)) {
            return;
        }

        var batchTable = primitive._batchTable;
        var attributeLocations = defaultValue(primitive._attributeLocations, defaultAttributeLocations);

        var pickId = primitive._pickId;
        var vertexShaderSource = primitive._vertexShaderSource;
        var fragmentShaderSource = primitive._fragmentShaderSource;
        if (defined(vertexShaderSource)) {
            primitive._sp = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : vertexShaderSource,
                fragmentShaderSource : fragmentShaderSource,
                attributeLocations : attributeLocations
            });
            primitive._spStencil = primitive._sp;

            fragmentShaderSource = ShaderSource.replaceMain(fragmentShaderSource, 'czm_non_pick_main');
            fragmentShaderSource =
                fragmentShaderSource +
                'void main() \n' +
                '{ \n' +
                '    czm_non_pick_main(); \n' +
                '    gl_FragColor = ' + pickId + '; \n' +
                '} \n';
            primitive._spPick = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : vertexShaderSource,
                fragmentShaderSource : fragmentShaderSource,
                attributeLocations : attributeLocations
            });
            return;
        }

        var vsSource = batchTable.getVertexShaderCallback(false, 'a_batchId', undefined)(VectorTileVS);
        var fsSource = batchTable.getFragmentShaderCallback()(ShadowVolumeFS, false, undefined);

        pickId = batchTable.getPickId();

        var vs = new ShaderSource({
            sources : [vsSource]
        });
        var fs = new ShaderSource({
            defines : ['VECTOR_TILE'],
            sources : [fsSource]
        });

        primitive._sp = ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : attributeLocations
        });

        vs = new ShaderSource({
            sources : [VectorTileVS]
        });
        fs = new ShaderSource({
            defines : ['VECTOR_TILE'],
            sources : [ShadowVolumeFS]
        });

        primitive._spStencil = ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : attributeLocations
        });

        fsSource = ShaderSource.replaceMain(fsSource, 'czm_non_pick_main');
        fsSource =
            fsSource + '\n' +
            'void main() \n' +
            '{ \n' +
            '    czm_non_pick_main(); \n' +
            '    gl_FragColor = ' + pickId + '; \n' +
            '} \n';

        var pickVS = new ShaderSource({
            sources : [vsSource]
        });
        var pickFS = new ShaderSource({
            defines : ['VECTOR_TILE'],
            sources : [fsSource]
        });
        primitive._spPick = ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : pickVS,
            fragmentShaderSource : pickFS,
            attributeLocations : attributeLocations
        });
    }

    function getStencilPreloadRenderState(mask3DTiles) {
        var stencilFunction = mask3DTiles ? StencilFunction.EQUAL : StencilFunction.ALWAYS;
        return {
            colorMask : {
                red : false,
                green : false,
                blue : false,
                alpha : false
            },
            stencilTest : {
                enabled : true,
                frontFunction : stencilFunction,
                frontOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.DECREMENT_WRAP,
                    zPass : StencilOperation.DECREMENT_WRAP
                },
                backFunction : stencilFunction,
                backOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.INCREMENT_WRAP,
                    zPass : StencilOperation.INCREMENT_WRAP
                },
                reference : StencilConstants.CESIUM_3D_TILE_MASK,
                mask : StencilConstants.CESIUM_3D_TILE_MASK
            },
            stencilMask : StencilConstants.CLASSIFICATION_MASK,
            depthTest : {
                enabled : false
            },
            depthMask : false
        };
    }

    function getStencilDepthRenderState(mask3DTiles) {
        var stencilFunction = mask3DTiles ? StencilFunction.EQUAL : StencilFunction.ALWAYS;
        return {
            colorMask : {
                red : false,
                green : false,
                blue : false,
                alpha : false
            },
            stencilTest : {
                enabled : true,
                frontFunction : stencilFunction,
                frontOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.KEEP,
                    zPass : StencilOperation.INCREMENT_WRAP
                },
                backFunction : stencilFunction,
                backOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.KEEP,
                    zPass : StencilOperation.DECREMENT_WRAP
                },
                reference : StencilConstants.CESIUM_3D_TILE_MASK,
                mask : StencilConstants.CESIUM_3D_TILE_MASK
            },
            stencilMask : StencilConstants.CLASSIFICATION_MASK,
            depthTest : {
                enabled : true,
                func : DepthFunction.LESS_OR_EQUAL
            },
            depthMask : false
        };
    }

    var colorRenderState = {
        stencilTest : {
            enabled : true,
            frontFunction : StencilFunction.NOT_EQUAL,
            frontOperation : {
                fail : StencilOperation.KEEP,
                zFail : StencilOperation.KEEP,
                zPass : StencilOperation.DECREMENT_WRAP
            },
            backFunction : StencilFunction.NOT_EQUAL,
            backOperation : {
                fail : StencilOperation.KEEP,
                zFail : StencilOperation.KEEP,
                zPass : StencilOperation.DECREMENT_WRAP
            },
            reference : 0,
            mask : StencilConstants.CLASSIFICATION_MASK
        },
        stencilMask : StencilConstants.CLASSIFICATION_MASK,
        depthTest : {
            enabled : false
        },
        depthMask : false,
        blending : BlendingState.ALPHA_BLEND
    };

    var pickRenderState = {
        stencilTest : {
            enabled : true,
            frontFunction : StencilFunction.NOT_EQUAL,
            frontOperation : {
                fail : StencilOperation.KEEP,
                zFail : StencilOperation.KEEP,
                zPass : StencilOperation.DECREMENT_WRAP
            },
            backFunction : StencilFunction.NOT_EQUAL,
            backOperation : {
                fail : StencilOperation.KEEP,
                zFail : StencilOperation.KEEP,
                zPass : StencilOperation.DECREMENT_WRAP
            },
            reference : 0,
            mask : StencilConstants.CLASSIFICATION_MASK
        },
        stencilMask : StencilConstants.CLASSIFICATION_MASK,
        depthTest : {
            enabled : false
        },
        depthMask : false
    };

    function createRenderStates(primitive) {
        if (defined(primitive._rsStencilPreloadPass)) {
            return;
        }

        primitive._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(false));
        primitive._rsStencilPreloadPass3DTiles = RenderState.fromCache(getStencilPreloadRenderState(true));
        primitive._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(false));
        primitive._rsStencilDepthPass3DTiles = RenderState.fromCache(getStencilDepthRenderState(true));
        primitive._rsColorPass = RenderState.fromCache(colorRenderState);
        primitive._rsPickPass = RenderState.fromCache(pickRenderState);
    }

    var modifiedModelViewScratch = new Matrix4();
    var rtcScratch = new Cartesian3();

    function createUniformMap(primitive, context) {
        if (defined(primitive._uniformMap)) {
            return;
        }

        var uniformMap = {
            u_modifiedModelViewProjection : function() {
                var viewMatrix = context.uniformState.view;
                var projectionMatrix = context.uniformState.projection;
                Matrix4.clone(viewMatrix, modifiedModelViewScratch);
                Matrix4.multiplyByPoint(modifiedModelViewScratch, primitive._center, rtcScratch);
                Matrix4.setTranslation(modifiedModelViewScratch, rtcScratch, modifiedModelViewScratch);
                Matrix4.multiply(projectionMatrix, modifiedModelViewScratch, modifiedModelViewScratch);
                return modifiedModelViewScratch;
            },
            u_highlightColor : function() {
                return primitive._highlightColor;
            }
        };

        primitive._uniformMap = primitive._batchTable.getUniformMapCallback()(uniformMap);
    }

    function copyIndicesCPU(indices, newIndices, currentOffset, offsets, counts, batchIds, batchIdLookUp) {
        var sizeInBytes = indices.constructor.BYTES_PER_ELEMENT;

        var batchedIdsLength = batchIds.length;
        for (var j = 0; j < batchedIdsLength; ++j) {
            var batchedId = batchIds[j];
            var index = batchIdLookUp[batchedId];
            var offset = offsets[index];
            var count = counts[index];

            var subarray = new indices.constructor(indices.buffer, sizeInBytes * offset, count);
            newIndices.set(subarray, currentOffset);

            offsets[index] = currentOffset;
            currentOffset += count;
        }

        return currentOffset;
    }

    function rebatchCPU(primitive, batchedIndices) {
        var indices = primitive._indices;
        var indexOffsets = primitive._indexOffsets;
        var indexCounts = primitive._indexCounts;
        var batchIdLookUp = primitive._batchIdLookUp;

        var newIndices = new indices.constructor(indices.length);

        var current = batchedIndices.pop();
        var newBatchedIndices = [current];

        var currentOffset = copyIndicesCPU(indices, newIndices, 0, indexOffsets, indexCounts, current.batchIds, batchIdLookUp);

        current.offset = 0;
        current.count = currentOffset;

        while (batchedIndices.length > 0) {
            var next = batchedIndices.pop();
            if (Color.equals(next.color, current.color)) {
                currentOffset = copyIndicesCPU(indices, newIndices, currentOffset, indexOffsets, indexCounts, next.batchIds, batchIdLookUp);
                current.batchIds = current.batchIds.concat(next.batchIds);
                current.count = currentOffset - current.offset;
            } else {
                var offset = currentOffset;
                currentOffset = copyIndicesCPU(indices, newIndices, currentOffset, indexOffsets, indexCounts, next.batchIds, batchIdLookUp);

                next.offset = offset;
                next.count = currentOffset - offset;
                newBatchedIndices.push(next);
                current = next;
            }
        }

        primitive._va.indexBuffer.copyFromArrayView(newIndices);

        primitive._indices = newIndices;
        primitive._batchedIndices = newBatchedIndices;
    }

    function copyIndicesGPU(readBuffer, writeBuffer, currentOffset, offsets, counts, batchIds, batchIdLookUp) {
        var sizeInBytes = readBuffer.bytesPerIndex;

        var batchedIdsLength = batchIds.length;
        for (var j = 0; j < batchedIdsLength; ++j) {
            var batchedId = batchIds[j];
            var index = batchIdLookUp[batchedId];
            var offset = offsets[index];
            var count = counts[index];

            writeBuffer.copyFromBuffer(readBuffer, offset * sizeInBytes, currentOffset * sizeInBytes, count * sizeInBytes);

            offsets[index] = currentOffset;
            currentOffset += count;
        }

        return currentOffset;
    }

    function rebatchGPU(primitive, batchedIndices) {
        var indexOffsets = primitive._indexOffsets;
        var indexCounts = primitive._indexCounts;
        var batchIdLookUp = primitive._batchIdLookUp;

        var current = batchedIndices.pop();
        var newBatchedIndices = [current];

        var readBuffer = primitive._va.indexBuffer;
        var writeBuffer = primitive._vaSwap.indexBuffer;

        var currentOffset = copyIndicesGPU(readBuffer, writeBuffer, 0, indexOffsets, indexCounts, current.batchIds, batchIdLookUp);

        current.offset = 0;
        current.count = currentOffset;

        while (batchedIndices.length > 0) {
            var next = batchedIndices.pop();
            if (Color.equals(next.color, current.color)) {
                currentOffset = copyIndicesGPU(readBuffer, writeBuffer, currentOffset, indexOffsets, indexCounts, next.batchIds, batchIdLookUp);
                current.batchIds = current.batchIds.concat(next.batchIds);
                current.count = currentOffset - current.offset;
            } else {
                var offset = currentOffset;
                currentOffset = copyIndicesGPU(readBuffer, writeBuffer, currentOffset, indexOffsets, indexCounts, next.batchIds, batchIdLookUp);
                next.offset = offset;
                next.count = currentOffset - offset;
                newBatchedIndices.push(next);
                current = next;
            }
        }

        var temp = primitive._va;
        primitive._va = primitive._vaSwap;
        primitive._vaSwap = temp;

        primitive._batchedIndices = newBatchedIndices;
    }

    function compareColors(a, b) {
        return b.color.toRgba() - a.color.toRgba();
    }

    // PERFORMANCE_IDEA: For WebGL 2, we can use copyBufferSubData for buffer-to-buffer copies.
    // PERFORMANCE_IDEA: Not supported, but we could use glMultiDrawElements here.
    function rebatchCommands(primitive, context) {
        if (!primitive._batchDirty) {
            return false;
        }

        var batchedIndices = primitive._batchedIndices;
        var length = batchedIndices.length;

        var needToRebatch = false;
        var colorCounts = {};

        for (var i = 0; i < length; ++i) {
            var color = batchedIndices[i].color;
            var rgba = color.toRgba();
            if (defined(colorCounts[rgba])) {
                needToRebatch = true;
                break;
            } else {
                colorCounts[rgba] = true;
            }
        }

        if (!needToRebatch) {
            primitive._batchDirty = false;
            return false;
        }

        if (needToRebatch && !primitive.forceRebatch && primitive._framesSinceLastRebatch < 120) {
            ++primitive._framesSinceLastRebatch;
            return;
        }

        batchedIndices.sort(compareColors);

        if (context.webgl2) {
            rebatchGPU(primitive, batchedIndices);
        } else {
            rebatchCPU(primitive, batchedIndices);
        }

        primitive._framesSinceLastRebatch = 0;
        primitive._batchDirty = false;
        primitive._pickCommandsDirty = true;
        primitive._wireframeDirty = true;
        return true;
    }

    function createColorCommands(primitive, context) {
        var needsRebatch = rebatchCommands(primitive, context);

        var commands = primitive._commands;
        var batchedIndices = primitive._batchedIndices;
        var length = batchedIndices.length;
        var commandsLength = length * 3;

        if (defined(commands) &&
            !needsRebatch &&
            commands.length === commandsLength) {
            return;
        }

        commands.length = commandsLength;

        var vertexArray = primitive._va;
        var sp = primitive._sp;
        var modelMatrix = defaultValue(primitive._modelMatrix, Matrix4.IDENTITY);
        var uniformMap = primitive._uniformMap;
        var bv = primitive._boundingVolume;

        for (var j = 0; j < length; ++j) {
            var offset = batchedIndices[j].offset;
            var count = batchedIndices[j].count;

            var stencilPreloadCommand = commands[j * 3];
            if (!defined(stencilPreloadCommand)) {
                stencilPreloadCommand = commands[j * 3] = new DrawCommand({
                    owner : primitive
                });
            }

            stencilPreloadCommand.vertexArray = vertexArray;
            stencilPreloadCommand.modelMatrix = modelMatrix;
            stencilPreloadCommand.offset = offset;
            stencilPreloadCommand.count = count;
            stencilPreloadCommand.renderState = primitive._rsStencilPreloadPass;
            stencilPreloadCommand.shaderProgram = sp;
            stencilPreloadCommand.uniformMap = uniformMap;
            stencilPreloadCommand.boundingVolume = bv;
            stencilPreloadCommand.cull = false;
            stencilPreloadCommand.pass = Pass.TERRAIN_CLASSIFICATION;

            var stencilPreloadDerivedCommand = DrawCommand.shallowClone(stencilPreloadCommand, stencilPreloadCommand.derivedCommands.tileset);
            stencilPreloadDerivedCommand.renderState = primitive._rsStencilPreloadPass3DTiles;
            stencilPreloadDerivedCommand.pass = Pass.CESIUM_3D_TILE_CLASSIFICATION;
            stencilPreloadCommand.derivedCommands.tileset = stencilPreloadDerivedCommand;

            var stencilDepthCommand = commands[j * 3 + 1];
            if (!defined(stencilDepthCommand)) {
                stencilDepthCommand = commands[j * 3 + 1] = new DrawCommand({
                    owner : primitive
                });
            }

            stencilDepthCommand.vertexArray = vertexArray;
            stencilDepthCommand.modelMatrix = modelMatrix;
            stencilDepthCommand.offset = offset;
            stencilDepthCommand.count = count;
            stencilDepthCommand.renderState = primitive._rsStencilDepthPass;
            stencilDepthCommand.shaderProgram = sp;
            stencilDepthCommand.uniformMap = uniformMap;
            stencilDepthCommand.boundingVolume = bv;
            stencilDepthCommand.cull = false;
            stencilDepthCommand.pass = Pass.TERRAIN_CLASSIFICATION;

            var stencilDepthDerivedCommand = DrawCommand.shallowClone(stencilDepthCommand, stencilDepthCommand.derivedCommands.tileset);
            stencilDepthDerivedCommand.renderState = primitive._rsStencilDepthPass3DTiles;
            stencilDepthDerivedCommand.pass = Pass.CESIUM_3D_TILE_CLASSIFICATION;
            stencilDepthCommand.derivedCommands.tileset = stencilDepthDerivedCommand;

            var colorCommand = commands[j * 3 + 2];
            if (!defined(colorCommand)) {
                colorCommand = commands[j * 3 + 2] = new DrawCommand({
                    owner : primitive
                });
            }

            colorCommand.vertexArray = vertexArray;
            colorCommand.modelMatrix = modelMatrix;
            colorCommand.offset = offset;
            colorCommand.count = count;
            colorCommand.renderState = primitive._rsColorPass;
            colorCommand.shaderProgram = sp;
            colorCommand.uniformMap = uniformMap;
            colorCommand.boundingVolume = bv;
            colorCommand.cull = false;
            colorCommand.pass = Pass.TERRAIN_CLASSIFICATION;

            var colorDerivedCommand = DrawCommand.shallowClone(colorCommand, colorCommand.derivedCommands.tileset);
            colorDerivedCommand.pass = Pass.CESIUM_3D_TILE_CLASSIFICATION;
            colorCommand.derivedCommands.tileset = colorDerivedCommand;
        }

        primitive._commandsDirty = true;
    }

    function createColorCommandsIgnoreShow(primitive, frameState) {
        if (primitive.classificationType === ClassificationType.TERRAIN ||
            !frameState.invertClassification ||
            (defined(primitive._commandsIgnoreShow) && !primitive._commandsDirty)) {
            return;
        }

        var commands = primitive._commands;
        var commandsIgnoreShow = primitive._commandsIgnoreShow;
        var spStencil = primitive._spStencil;

        var commandsLength = commands.length;
        var length = commandsIgnoreShow.length = commandsLength / 3 * 2;

        var commandIndex = 0;
        for (var j = 0; j < length; j += 2) {
            var commandIgnoreShow = commandsIgnoreShow[j] = DrawCommand.shallowClone(commands[commandIndex], commandsIgnoreShow[j]);
            commandIgnoreShow.shaderProgram = spStencil;
            commandIgnoreShow.pass = Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW;

            commandIgnoreShow = commandsIgnoreShow[j + 1] = DrawCommand.shallowClone(commands[commandIndex + 1], commandsIgnoreShow[j + 1]);
            commandIgnoreShow.shaderProgram = spStencil;
            commandIgnoreShow.pass = Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW;

            commandIndex += 3;
        }

        primitive._commandsDirty = false;
    }

    function createPickCommands(primitive) {
        if (!primitive._pickCommandsDirty) {
            return;
        }

        var length = primitive._indexOffsets.length;
        var pickCommands = primitive._pickCommands;
        pickCommands.length = length * 3;

        var vertexArray = primitive._va;
        var spStencil = primitive._spStencil;
        var spPick = primitive._spPick;
        var modelMatrix = defaultValue(primitive._modelMatrix, Matrix4.IDENTITY);
        var uniformMap = primitive._uniformMap;

        for (var j = 0; j < length; ++j) {
            var offset = primitive._indexOffsets[j];
            var count = primitive._indexCounts[j];
            var bv = defined(primitive._boundingVolumes) ? primitive._boundingVolumes[j] : primitive.boundingVolume;

            var stencilPreloadCommand = pickCommands[j * 3];
            if (!defined(stencilPreloadCommand)) {
                stencilPreloadCommand = pickCommands[j * 3] = new DrawCommand({
                    owner : primitive,
                    pickOnly : true
                });
            }

            stencilPreloadCommand.vertexArray = vertexArray;
            stencilPreloadCommand.modelMatrix = modelMatrix;
            stencilPreloadCommand.offset = offset;
            stencilPreloadCommand.count = count;
            stencilPreloadCommand.renderState = primitive._rsStencilPreloadPass;
            stencilPreloadCommand.shaderProgram = spStencil;
            stencilPreloadCommand.uniformMap = uniformMap;
            stencilPreloadCommand.boundingVolume = bv;
            stencilPreloadCommand.pass = Pass.TERRAIN_CLASSIFICATION;

            var stencilPreloadDerivedCommand = DrawCommand.shallowClone(stencilPreloadCommand, stencilPreloadCommand.derivedCommands.tileset);
            stencilPreloadDerivedCommand.renderState = primitive._rsStencilPreloadPass3DTiles;
            stencilPreloadDerivedCommand.pass = Pass.CESIUM_3D_TILE_CLASSIFICATION;
            stencilPreloadCommand.derivedCommands.tileset = stencilPreloadDerivedCommand;

            var stencilDepthCommand = pickCommands[j * 3 + 1];
            if (!defined(stencilDepthCommand)) {
                stencilDepthCommand = pickCommands[j * 3 + 1] = new DrawCommand({
                    owner : primitive,
                    pickOnly : true
                });
            }

            stencilDepthCommand.vertexArray = vertexArray;
            stencilDepthCommand.modelMatrix = modelMatrix;
            stencilDepthCommand.offset = offset;
            stencilDepthCommand.count = count;
            stencilDepthCommand.renderState = primitive._rsStencilDepthPass;
            stencilDepthCommand.shaderProgram = spStencil;
            stencilDepthCommand.uniformMap = uniformMap;
            stencilDepthCommand.boundingVolume = bv;
            stencilDepthCommand.pass = Pass.TERRAIN_CLASSIFICATION;

            var stencilDepthDerivedCommand = DrawCommand.shallowClone(stencilDepthCommand, stencilDepthCommand.derivedCommands.tileset);
            stencilDepthDerivedCommand.renderState = primitive._rsStencilDepthPass3DTiles;
            stencilDepthDerivedCommand.pass = Pass.CESIUM_3D_TILE_CLASSIFICATION;
            stencilDepthCommand.derivedCommands.tileset = stencilDepthDerivedCommand;

            var colorCommand = pickCommands[j * 3 + 2];
            if (!defined(colorCommand)) {
                colorCommand = pickCommands[j * 3 + 2] = new DrawCommand({
                    owner : primitive,
                    pickOnly : true
                });
            }

            colorCommand.vertexArray = vertexArray;
            colorCommand.modelMatrix = modelMatrix;
            colorCommand.offset = offset;
            colorCommand.count = count;
            colorCommand.renderState = primitive._rsPickPass;
            colorCommand.shaderProgram = spPick;
            colorCommand.uniformMap = uniformMap;
            colorCommand.boundingVolume = bv;
            colorCommand.pass = Pass.TERRAIN_CLASSIFICATION;

            var colorDerivedCommand = DrawCommand.shallowClone(colorCommand, colorCommand.derivedCommands.tileset);
            colorDerivedCommand.pass = Pass.CESIUM_3D_TILE_CLASSIFICATION;
            colorCommand.derivedCommands.tileset = colorDerivedCommand;
        }

        primitive._pickCommandsDirty = false;
    }

    /**
     * Creates features for each mesh and places it at the batch id index of features.
     *
     * @param {Vector3DTileContent} content The vector tile content.
     * @param {Cesium3DTileFeature[]} features An array of features where the polygon features will be placed.
     */
    Vector3DTilePrimitive.prototype.createFeatures = function(content, features) {
        var batchIds = this._batchIds;
        var length = batchIds.length;
        for (var i = 0; i < length; ++i) {
            var batchId = batchIds[i];
            features[batchId] = new Cesium3DTileFeature(content, batchId);
        }
    };

    /**
     * Colors the entire tile when enabled is true. The resulting color will be (mesh batch table color * color).
     *
     * @param {Boolean} enabled Whether to enable debug coloring.
     * @param {Color} color The debug color.
     */
    Vector3DTilePrimitive.prototype.applyDebugSettings = function(enabled, color) {
        this._highlightColor = enabled ? color : this._constantColor;
    };

    function clearStyle(polygons, features) {
        polygons._updatingAllCommands = true;

        var batchIds = polygons._batchIds;
        var length = batchIds.length;
        var i;

        for (i = 0; i < length; ++i) {
            var batchId = batchIds[i];
            var feature = features[batchId];

            feature.show = true;
            feature.color = Color.WHITE;
        }

        var batchedIndices = polygons._batchedIndices;
        length = batchedIndices.length;

        for (i = 0; i < length; ++i) {
            batchedIndices[i].color = Color.clone(Color.WHITE);
        }

        polygons._updatingAllCommands = false;
        polygons._batchDirty = true;
    }

    var scratchColor = new Color();

    var DEFAULT_COLOR_VALUE = Color.WHITE;
    var DEFAULT_SHOW_VALUE = true;

    var complexExpressionReg = /\$/;

    /**
     * Apply a style to the content.
     *
     * @param {Cesium3DTileStyle} style The style.
     * @param {Cesium3DTileFeature[]} features The array of features.
     */
    Vector3DTilePrimitive.prototype.applyStyle = function(style, features) {
        if (!defined(style)) {
            clearStyle(this, features);
            return;
        }

        var colorExpression = style.color;
        var isSimpleStyle = colorExpression instanceof Expression && !complexExpressionReg.test(colorExpression.expression);
        this._updatingAllCommands = isSimpleStyle;

        var batchIds = this._batchIds;
        var length = batchIds.length;
        var i;

        for (i = 0; i < length; ++i) {
            var batchId = batchIds[i];
            var feature = features[batchId];

            feature.color = defined(style.color) ? style.color.evaluateColor(feature, scratchColor) : DEFAULT_COLOR_VALUE;
            feature.show = defined(style.show) ? style.show.evaluate(feature) : DEFAULT_SHOW_VALUE;
        }

        if (isSimpleStyle) {
            var batchedIndices = this._batchedIndices;
            length = batchedIndices.length;

            for (i = 0; i < length; ++i) {
                batchedIndices[i].color = Color.clone(Color.WHITE);
            }

            this._updatingAllCommands = false;
            this._batchDirty = true;
        }
    };

    /**
     * Call when updating the color of a mesh with batchId changes color. The meshes will need to be re-batched
     * on the next update.
     *
     * @param {Number} batchId The batch id of the meshes whose color has changed.
     * @param {Color} color The new polygon color.
     */
    Vector3DTilePrimitive.prototype.updateCommands = function(batchId, color) {
        if (this._updatingAllCommands) {
            return;
        }

        var batchIdLookUp = this._batchIdLookUp;
        var index = batchIdLookUp[batchId];
        if (!defined(index)) {
            return;
        }

        var indexOffsets = this._indexOffsets;
        var indexCounts = this._indexCounts;

        var offset = indexOffsets[index];
        var count = indexCounts[index];

        var batchedIndices = this._batchedIndices;
        var length = batchedIndices.length;

        var i;
        for (i = 0; i < length; ++i) {
            var batchedOffset = batchedIndices[i].offset;
            var batchedCount = batchedIndices[i].count;

            if (offset >= batchedOffset && offset < batchedOffset + batchedCount) {
                break;
            }
        }

        batchedIndices.push(new Vector3DTileBatch({
            color : Color.clone(color),
            offset : offset,
            count : count,
            batchIds : [batchId]
        }));

        var startIds = [];
        var endIds = [];

        var batchIds = batchedIndices[i].batchIds;
        var batchIdsLength = batchIds.length;

        for (var j = 0; j < batchIdsLength; ++j) {
            var id = batchIds[j];
            if (id === batchId) {
                continue;
            }

            var offsetIndex = batchIdLookUp[id];
            if (indexOffsets[offsetIndex] < offset) {
                startIds.push(id);
            } else {
                endIds.push(id);
            }
        }

        if (endIds.length !== 0) {
            batchedIndices.push(new Vector3DTileBatch({
                color : Color.clone(batchedIndices[i].color),
                offset : offset + count,
                count : batchedIndices[i].offset + batchedIndices[i].count - (offset + count),
                batchIds : endIds
            }));
        }

        if (startIds.length !== 0) {
            batchedIndices[i].count = offset - batchedIndices[i].offset;
            batchedIndices[i].batchIds = startIds;
        } else {
            batchedIndices.splice(i, 1);
        }

        this._batchDirty = true;
    };

    function queueCommands(primitive, frameState, commands, commandsIgnoreShow) {
        var classificationType = primitive.classificationType;
        var queueTerrainCommands = (classificationType !== ClassificationType.CESIUM_3D_TILE);
        var queue3DTilesCommands = (classificationType !== ClassificationType.TERRAIN);

        var commandList = frameState.commandList;
        var commandLength = commands.length;
        var command;
        var i;
        for (i = 0; i < commandLength; ++i) {
            if (queueTerrainCommands) {
                command = commands[i];
                command.pass = Pass.TERRAIN_CLASSIFICATION;
                commandList.push(command);
            }
            if (queue3DTilesCommands) {
                command = commands[i].derivedCommands.tileset;
                command.pass = Pass.CESIUM_3D_TILE_CLASSIFICATION;
                commandList.push(command);
            }
        }

        if (!frameState.invertClassification || !defined(commandsIgnoreShow)) {
            return;
        }

        commandLength = commandsIgnoreShow.length;
        for (i = 0; i < commandLength; ++i) {
            commandList.push(commandsIgnoreShow[i]);
        }
    }

    function queueWireframeCommands(frameState, commands) {
        var commandList = frameState.commandList;
        var commandLength = commands.length;
        for (var i = 0; i < commandLength; i += 3) {
            var command = commands[i + 2];
            command.pass = Pass.OPAQUE;
            commandList.push(command);
        }
    }

    function updateWireframe(primitive) {
        var earlyExit = primitive.debugWireframe === primitive._debugWireframe;
        earlyExit = earlyExit && !(primitive.debugWireframe && primitive._wireframeDirty);
        if (earlyExit) {
            return;
        }

        if (!defined(primitive._rsWireframe)) {
            primitive._rsWireframe = RenderState.fromCache({});
        }

        var rs;
        var type;

        if (primitive.debugWireframe) {
            rs = primitive._rsWireframe;
            type = PrimitiveType.LINES;
        } else {
            rs = primitive._rsColorPass;
            type = PrimitiveType.TRIANGLES;
        }

        var commands = primitive._commands;
        var commandLength = commands.length;
        for (var i = 0; i < commandLength; i += 3) {
            var command = commands[i + 2];
            command.renderState = rs;
            command.primitiveType = type;
        }

        primitive._debugWireframe = primitive.debugWireframe;
        primitive._wireframeDirty = false;
    }

    /**
     * Updates the batches and queues the commands for rendering.
     *
     * @param {FrameState} frameState The current frame state.
     */
    Vector3DTilePrimitive.prototype.update = function(frameState) {
        var context = frameState.context;

        createVertexArray(this, context);
        createShaders(this, context);
        createRenderStates(this);
        createUniformMap(this, context);

        var passes = frameState.passes;
        if (passes.render) {
            createColorCommands(this, context);
            createColorCommandsIgnoreShow(this, frameState);
            updateWireframe(this);

            if (this._debugWireframe) {
                queueWireframeCommands(frameState, this._commands);
            } else {
                queueCommands(this, frameState, this._commands, this._commandsIgnoreShow);
            }
        }

        if (passes.pick) {
            createPickCommands(this);
            queueCommands(this, frameState, this._pickCommands);
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <p>
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     * </p>
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     */
    Vector3DTilePrimitive.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <p>
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     * </p>
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    Vector3DTilePrimitive.prototype.destroy = function() {
        this._va = this._va && this._va.destroy();
        this._sp = this._sp && this._sp.destroy();
        this._spPick = this._spPick && this._spPick.destroy();
        this._vaSwap = this._vaSwap && this._vaSwap.destroy();
        return destroyObject(this);
    };

    return Vector3DTilePrimitive;
});


text:
#include "pch.h"
#include <gui/cairo_painter.hpp>

#if defined(HAS_CAIRO)

#pragma comment (lib, "cairo.lib")

#include <gui/image.hpp>
#include <gui/node.hpp>
#include <net/utf8.hpp>

#include <assert.h>
#define ASSERT(x) assert(x)

namespace gui { namespace cairo {

	template <size_t Index> static float channel(colorref clr)
	{
		auto ch = (clr >> (8 * Index)) & 0xFF;
		return (float)(ch / 255.0);
	}

	painter::painter(cairo_surface_t* surface, ratio zoom, const pixels& fontSize, const std::string& fontFamily)
		: base::painter{ zoom, fontSize, fontFamily }
		, m_surface{ surface }
		, m_cr{ cairo_create(surface) }
		, m_color(0)
	{
		selectFont(fontSize, fontFamily, 400, false, false);
	}

	painter::~painter()
	{
		cairo_destroy(m_cr);
	}

	void painter::paintImage(const image_ref* /*img*/, const pixels& /*width*/, const pixels& /*height*/)
	{
	}

	void painter::paintString(const std::string& text)
	{
		if (text.empty())
			return;

		cairo_set_source_rgb(m_cr, channel<0>(m_color), channel<1>(m_color), channel<2>(m_color));

		cairo_text_extents_t te;
		cairo_text_extents(m_cr, text.c_str(), &te);
		cairo_move_to(m_cr, zoom().scaleF(origin().x) - te.x_bearing, zoom().scaleF(origin().y) - te.y_bearing);
		cairo_show_text(m_cr, text.c_str());
	}

	size painter::measureString(const std::string& text)
	{
		cairo_text_extents_t te;
		cairo_text_extents(m_cr, text.c_str(), &te);
		return{ zoom().invert(te.width), zoom().invert(te.height) };
	}

	template<typename T>
	static inline T xp2dev(const ratio& r, T value)
	{
		return r.num * value / r.denom;
	}

	struct RectF {
		point pt;
		size sz;
	};

	static void FillSolidRect(cairo_t* cr, const RectF& rect, const ratio& zoom, colorref clr)
	{
		cairo_set_source_rgb(cr, channel<0>(clr), channel<1>(clr), channel<2>(clr));
		cairo_rectangle(cr, zoom.scaleD(rect.pt.x), zoom.scaleD(rect.pt.y), zoom.scaleD(rect.sz.width), zoom.scaleD(rect.sz.height));
		cairo_fill(cr);
	}

	void painter::fillRectangle(colorref color, const point& pt, const size& size)
	{
		FillSolidRect(m_cr, { origin() + pt, size }, zoom(), color);
	}

	void painter::drawBorder(line_style /*style*/, colorref color, const gui::point& pt, const gui::size& size)
	{
		FillSolidRect(m_cr, { origin() + pt, size }, zoom(), color);
	}

	static int gui2raw(weight w)
	{
		ASSERT(w != weight::bolder);
		ASSERT(w != weight::lighter);
		switch (w) {
		case weight::normal: return 400;
		case weight::bold: return 700;
		default:
			break;
		}

		return (int)w;
	}

	void painter::setFont(const pixels& fontSize, const std::string& fontFamily, gui::weight weight, bool italic, bool underline)
	{
		selectFont(fontSize, fontFamily, gui2raw(weight), italic, underline);
	}

	void painter::setColor(colorref color)
	{
		m_color = color;
	}

	colorref painter::getColor() const
	{
		return m_color;
	}

	void painter::selectFont(const pixels& fontSize, const std::string& fontFamily, int weight, bool italic, bool /*underline*/)
	{
		cairo_select_font_face(m_cr, fontFamily.c_str(),
			italic ? CAIRO_FONT_SLANT_ITALIC : CAIRO_FONT_SLANT_NORMAL,
			weight > 650 ? CAIRO_FONT_WEIGHT_BOLD : CAIRO_FONT_WEIGHT_NORMAL);

		cairo_set_font_size(m_cr, zoom().scaleD(fontSize));
	}
}};

#endif // defined(HAS_CAIRO)


text:
// Package cloudstorage registers the "googlecloudstorage" blob storage type, storing blobs
// on Google Cloud Storage (not Google Drive).
// See https://cloud.google.com/products/cloud-storage
package cloudstorage

import (
	"bytes"
	"context"
	"crypto/sha1"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path"
	"strings"
	"time"

	"perkeep.org/pkg/blob"
	"perkeep.org/pkg/blobserver"
	"perkeep.org/pkg/blobserver/memory"
	"perkeep.org/pkg/constants"

	"cloud.google.com/go/storage"
	"go4.org/cloud/google/gcsutil"
	"go4.org/ctxutil"
	"go4.org/jsonconfig"
	"go4.org/oauthutil"
	"go4.org/syncutil"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/option"
)

type Storage struct {
	bucket string // the gs bucket containing blobs
	// optional "directory" where the blobs are stored, instead of at the root of the bucket.
	// gcs is actually flat, which in effect just means that all the objects should have this
	// dirPrefix as a prefix of their key.
	// If non empty, it should be a slash separated path with a trailing slash and no starting
	// slash.
	dirPrefix string
	client    *storage.Client
	cache     *memory.Storage // or nil for no cache

	// an OAuth-authenticated HTTP client, for methods that can't yet use a
	// *storage.Client
	baseHTTPClient *http.Client

	// For blobserver.Generationer:
	genTime   time.Time
	genRandom string
}

var (
	_ blob.SubFetcher               = (*Storage)(nil)
	_ blobserver.Generationer       = (*Storage)(nil)
	_ blobserver.MaxEnumerateConfig = (*Storage)(nil)
)

func (s *Storage) MaxEnumerate() int { return 1000 }

func (s *Storage) StorageGeneration() (time.Time, string, error) {
	return s.genTime, s.genRandom, nil
}
func (s *Storage) ResetStorageGeneration() error { return errors.New("not supported") }

func newFromConfig(_ blobserver.Loader, config jsonconfig.Obj) (blobserver.Storage, error) {
	var (
		auth      = config.RequiredObject("auth")
		bucket    = config.RequiredString("bucket")
		cacheSize = config.OptionalInt64("cacheSize", 32<<20)

		clientID     = auth.RequiredString("client_id") // or "auto" for service accounts
		clientSecret = auth.OptionalString("client_secret", "")
		refreshToken = auth.OptionalString("refresh_token", "")
	)

	if err := config.Validate(); err != nil {
		return nil, err
	}
	if err := auth.Validate(); err != nil {
		return nil, err
	}

	var dirPrefix string
	if parts := strings.SplitN(bucket, "/", 2); len(parts) > 1 {
		dirPrefix = parts[1]
		bucket = parts[0]
	}
	if dirPrefix != "" && !strings.HasSuffix(dirPrefix, "/") {
		dirPrefix += "/"
	}
	gs := &Storage{
		bucket:    bucket,
		dirPrefix: dirPrefix,
	}

	var (
		ctx = context.Background()
		ts  oauth2.TokenSource
		cl  *storage.Client
		err error
	)
	if clientID == "auto" {
		ts, err = google.DefaultTokenSource(ctx, storage.ScopeReadWrite)
		if err != nil {
			return nil, err
		}
		cl, err = storage.NewClient(ctx)
		if err != nil {
			return nil, err
		}
	} else {
		if clientSecret == "" {
			return nil, errors.New("missing required parameter 'client_secret'")
		}
		if refreshToken == "" {
			return nil, errors.New("missing required parameter 'refresh_token'")
		}
		ts = oauthutil.NewRefreshTokenSource(&oauth2.Config{
			Scopes:       []string{storage.ScopeReadWrite},
			Endpoint:     google.Endpoint,
			ClientID:     clientID,
			ClientSecret: clientSecret,
			RedirectURL:  oauthutil.TitleBarRedirectURL,
		}, refreshToken)
		cl, err = storage.NewClient(ctx, option.WithTokenSource(ts))
		if err != nil {
			return nil, err
		}
	}

	gs.baseHTTPClient = oauth2.NewClient(ctx, ts)
	gs.client = cl

	if cacheSize != 0 {
		gs.cache = memory.NewCache(cacheSize)
	}

	ba, err := gs.client.Bucket(gs.bucket).Attrs(ctx)
	if err != nil {
		return nil, fmt.Errorf("error statting bucket %q: %v", gs.bucket, err)
	}
	hash := sha1.New()
	fmt.Fprintf(hash, "%v%v", ba.Created, ba.MetaGeneration)
	gs.genRandom = fmt.Sprintf("%x", hash.Sum(nil))
	gs.genTime = ba.Created

	return gs, nil
}

// TODO(mpl, bradfitz): use a *storage.Client in EnumerateBlobs, instead of hitting the
// XML API, once we have an efficient replacement for the "marker" from the XML API. See
// https://github.com/GoogleCloudPlatform/gcloud-golang/issues/197

func (s *Storage) EnumerateBlobs(ctx context.Context, dest chan<- blob.SizedRef, after string, limit int) error {
	defer close(dest)
	ectx := context.WithValue(ctx, ctxutil.HTTPClient, s.baseHTTPClient)
	objs, err := gcsutil.EnumerateObjects(ectx, s.bucket, s.dirPrefix+after, limit)
	if err != nil {
		log.Printf("gstorage EnumerateObjects: %v", err)
		return err
	}
	for _, obj := range objs {
		dir, file := path.Split(obj.Name)
		if dir != s.dirPrefix {
			continue
		}
		br, ok := blob.Parse(file)
		if !ok {
			return fmt.Errorf("Non-Perkeep object named %q found in bucket", file)
		}
		select {
		case dest <- blob.SizedRef{Ref: br, Size: uint32(obj.Size)}:
		case <-ctx.Done():
			return ctx.Err()
		}
	}
	return nil
}

func (s *Storage) ReceiveBlob(ctx context.Context, br blob.Ref, source io.Reader) (blob.SizedRef, error) {
	var buf bytes.Buffer
	size, err := io.Copy(&buf, source)
	if err != nil {
		return blob.SizedRef{}, err
	}

	w := s.client.Bucket(s.bucket).Object(s.dirPrefix + br.String()).NewWriter(ctx)
	if _, err := io.Copy(w, bytes.NewReader(buf.Bytes())); err != nil {
		return blob.SizedRef{}, err
	}
	if err := w.Close(); err != nil {
		return blob.SizedRef{}, err
	}

	if s.cache != nil {
		// NoHash because it's already verified if we read it
		// without errors on the io.Copy above.
		blobserver.ReceiveNoHash(ctx, s.cache, br, bytes.NewReader(buf.Bytes()))
	}
	return blob.SizedRef{Ref: br, Size: uint32(size)}, nil
}

var statGate = syncutil.NewGate(20) // arbitrary cap

func (s *Storage) StatBlobs(ctx context.Context, blobs []blob.Ref, fn func(blob.SizedRef) error) error {
	// TODO: use cache
	return blobserver.StatBlobsParallelHelper(ctx, blobs, fn, statGate, func(br blob.Ref) (sb blob.SizedRef, err error) {
		attrs, err := s.client.Bucket(s.bucket).Object(s.dirPrefix + br.String()).Attrs(ctx)
		if err == storage.ErrObjectNotExist {
			return sb, nil
		}
		if err != nil {
			return sb, err
		}
		size := attrs.Size
		if size > constants.MaxBlobSize {
			return sb, fmt.Errorf("blob %s stat size too large (%d)", br, size)
		}
		return blob.SizedRef{Ref: br, Size: uint32(size)}, nil
	})
}

func (s *Storage) Fetch(ctx context.Context, br blob.Ref) (rc io.ReadCloser, size uint32, err error) {
	if s.cache != nil {
		if rc, size, err = s.cache.Fetch(ctx, br); err == nil {
			return
		}
	}
	r, err := s.client.Bucket(s.bucket).Object(s.dirPrefix + br.String()).NewReader(ctx)
	if err == storage.ErrObjectNotExist {
		return nil, 0, os.ErrNotExist
	}
	if err != nil {
		return nil, 0, err
	}
	if r.Size() >= 1<<32 {
		r.Close()
		return nil, 0, errors.New("object larger than a uint32")
	}
	size = uint32(r.Size())
	if size > constants.MaxBlobSize {
		r.Close()
		return nil, size, errors.New("object too big")
	}
	return r, size, nil
}

func (s *Storage) SubFetch(ctx context.Context, br blob.Ref, offset, length int64) (rc io.ReadCloser, err error) {
	if offset < 0 || length < 0 {
		return nil, blob.ErrNegativeSubFetch
	}
	ctx = context.WithValue(ctx, ctxutil.HTTPClient, s.baseHTTPClient)
	rc, err = gcsutil.GetPartialObject(ctx, gcsutil.Object{Bucket: s.bucket, Key: s.dirPrefix + br.String()}, offset, length)
	if err == gcsutil.ErrInvalidRange {
		return nil, blob.ErrOutOfRangeOffsetSubFetch
	}
	if err == storage.ErrObjectNotExist {
		return nil, os.ErrNotExist
	}
	return rc, err
}

func (s *Storage) RemoveBlobs(ctx context.Context, blobs []blob.Ref) error {
	if s.cache != nil {
		s.cache.RemoveBlobs(ctx, blobs)
	}
	gate := syncutil.NewGate(50) // arbitrary
	var grp syncutil.Group
	for i := range blobs {
		gate.Start()
		br := blobs[i]
		grp.Go(func() error {
			defer gate.Done()
			err := s.client.Bucket(s.bucket).Object(s.dirPrefix + br.String()).Delete(ctx)
			if err == storage.ErrObjectNotExist {
				return nil
			}
			return err
		})
	}
	return grp.Err()
}

func init() {
	blobserver.RegisterStorageConstructor("googlecloudstorage", blobserver.StorageConstructor(newFromConfig))
}