mirror of
https://github.com/googleforgames/open-match.git
synced 2025-04-06 23:08:55 +00:00
Compare commits
57 Commits
v0.1.0-alp
...
v0.2.0-alp
Author | SHA1 | Date | |
---|---|---|---|
4a8e018599 | |||
c1b5d44947 | |||
ae9db9fae8 | |||
104fbd19cd | |||
3b2571fced | |||
3fb17c5f22 | |||
3f42e3d986 | |||
0c74debbb3 | |||
1854ee0ba1 | |||
99d9d7e2b5 | |||
e286435e19 | |||
b17dccac3b | |||
b9bb0b1aeb | |||
a6f2edbbae | |||
55db5c5ba3 | |||
b4f696484f | |||
12935d2cab | |||
a0cff79878 | |||
7a3c5937f2 | |||
f430720d2f | |||
34010986f7 | |||
d8a8d16bfc | |||
243f53336c | |||
d188be60c8 | |||
f1541a8cee | |||
cd1c4c768e | |||
967b6cc695 | |||
906c0861c7 | |||
4e0bb5c07d | |||
b57dd3e668 | |||
b2897ca159 | |||
041f9d7409 | |||
79282dac10 | |||
8142c6efc4 | |||
5f8b0edcdd | |||
4e4be7049e | |||
008ac9c516 | |||
43f9548483 | |||
27b7591770 | |||
acbd4035db | |||
cd98a68628 | |||
038957b937 | |||
d36945b9af | |||
c7acbf4481 | |||
1ec8a03636 | |||
a42ecc0cd9 | |||
0a2ac0e7e9 | |||
3e1c696d80 | |||
008b435921 | |||
5b3a53f48e | |||
eac217a85a | |||
e0bf6ce8af | |||
19c61f4726 | |||
3bc443ad99 | |||
308235936d | |||
1226a8dfc2 | |||
86c1d4c9ee |
.gitignoreCHANGELOG.mdDockerfile.backendapiDockerfile.baseDockerfile.evaluatorDockerfile.frontendapiDockerfile.mmf_goDockerfile.mmf_phpDockerfile.mmf_py3Dockerfile.mmlogicapiREADME.mdcloudbuild_base.yamlcloudbuild_mmf.yamlcloudbuild_mmf_go.yamlcloudbuild_mmf_php.yamlcloudbuild_mmf_py3.yamlcloudbuild_mmlogicapi.yaml
api
protobuf-spec
LICENSEREADME.mdbackend.pb.gobackend.protofrontend.pb.gofrontend.protomessages.protommlogic.protoprotoc.sh
protoc_go.shcmd
backendapi
frontendapi/apisrv
mmforc
mmlogicapi
config
deployments/k8s
docs
examples
backendclient
evaluators/golang/simple
functions
golang
php/mmlogic-simple
python3/mmlogic-simple
install/yaml
internal
pb
set
statestorage/redis
1
.gitignore
vendored
1
.gitignore
vendored
@ -26,6 +26,7 @@ populations
|
||||
|
||||
# Discarded code snippets
|
||||
build.sh
|
||||
*-fast.yaml
|
||||
|
||||
# Dotnet Core ignores
|
||||
*.swp
|
||||
|
32
CHANGELOG.md
Normal file
32
CHANGELOG.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Release history
|
||||
|
||||
##v0.2.0 (alpha)
|
||||
This is a pretty large update. Custom MMFs or evaluators from 0.1.0 may need some tweaking to work with this version. Some Backend API function arguments have changed. Please join the [Slack channel](https://open-match.slack.com/) if you need help ([Signup link](https://join.slack.com/t/open-match/shared_invite/enQtNDM1NjcxNTY4MTgzLWQzMzE1MGY5YmYyYWY3ZjE2MjNjZTdmYmQ1ZTQzMmNiNGViYmQyN2M4ZmVkMDY2YzZlOTUwMTYwMzI1Y2I2MjU))!
|
||||
|
||||
v0.2.0 focused on adding additional functionality to Backend API calls and on **reducing the amount of boilerplate code required to make a custom Matchmaking Function**. For this, a new internal API for use by MMFs called the [Matchmaking Logic API (MMLogic API)](README.md#matchmaking-logic-mmlogic-api) has been added. Many of the core components and examples had to be updated to use the new Backend API arguments and the modules to support them, so we recommend you rebuild and redeploy all the components to use v0.2.0.
|
||||
|
||||
### Release notes
|
||||
- MMLogic API is now available. Deploy it to kubernetes using the [appropriate json file]() and check out the [gRPC API specification](api/protobuf-spec/mmlogic.proto) to see how to use it. To write a client against this API, you'll need to compile the protobuf files to your language of choice. There is an associated cloudbuild.yaml file and Dockerfile for it in the root directory.
|
||||
- When using the MMLogic API to filter players into pools, it will attempt to report back the number of players that matched the filters and how long the filters took to query state storage.
|
||||
- An [example MMF](examples/functions/python3/mmlogic-simple/harness.py) using it has been written in Python3. There is an associated cloudbuild.yaml file and Dockerfile for it in the root directory. By default the [example backend client](examples/backendclient/main.go) is now configured to use this MMF, so make sure you have it avaiable before you try to run the latest backend client.
|
||||
- An [example MMF](examples/functions/php/mmlogic-simple/harness.py) using it has been contributed by Ilya Hrankouski in PHP (thanks!). - The API specs have been split into separate files per API and the protobuf messages are in a separate file. Things were renamed slightly as a result, and you will need to update your API clients. The Frontend API hasn't had it's messages moved to the shared messages file yet, but this will happen in an upcoming version.
|
||||
- The [example golang MMF](examples/functions/golang/manual-simple/) has been updated to use the latest data schemas for MatchObjects, and renamed to `manual-simple` to denote that it is manually manipulating Redis, not using the MMLogic API.
|
||||
- The API specs have been split into separate files per API and the protobuf messages are in a separate file. Things were renamed slightly as a result, and you will need to update your API clients. The Frontend API hasn't had it's messages moved to the shared messages file yet, but this will happen in an upcoming version.
|
||||
- The message model for using the Backend API has changed slightly - for calls that make MatchObjects, the expectation is that you will provide a MatchObject with a few fields populated, and it will then be shuttled along through state storage to your MMF and back out again, with various processes 'filling in the blanks' of your MatchObject, which is then returned to your code calling the Backend API. Read the[gRPC API specification](api/protobuf-spec/backend.proto) for more information.
|
||||
- As part of this, compiled protobuf golang modules now live in the [`internal/pb`](internal/pb) directory. There's a handy [bash script](api/protoc-go.sh) for compiling them from the `api/protobuf-spec` directory into this new `internal/pb` directory for development in your local golang environment if you need it.
|
||||
- As part of this Backend API message shift and the advent of the MMLogic API, 'player pools' and 'rosters' are now first-class data structures in MatchObjects for those who wish to use them. You can ignore them if you like, but if you want to use some of the MMLogic API calls to automate tasks for you - things like filtering a pool of players according attributes or adding all the players in your rosters to the ignorelist so other MMFs don't try to grab them - you'll need to put your data into the [protobuf messages](api/protobuf-spec/messages.proto) so Open Match knows how to read them. The sample backend client [test profile JSON](examples/backendclient/profiles/testprofile.json)has been updated to use this format if you want to see an example.
|
||||
- Rosters were formerly space-delimited lists of player IDs. They are now first-class repeated protobuf message fields in the [Roster message format](api/protobuf-spec/messages.proto). That means that in most languages, you can access the roster as a list of players using your native language data structures (more info can be found in the [guide for using protocol buffers in your langauge of choice](https://developers.google.com/protocol-buffers/docs/reference/overview)). If you don't care about the new fields or the new functionality, you can just leave all the other fields but the player ID unset.
|
||||
- Open Match is transitioning to using [protocol buffer messages](https://developers.google.com/protocol-buffers/) as its internal data format. There is now a Redis state storage [golang module](internal/statestorage/redis/redispb/) for marshaling and unmarshaling MatchObject messages to and from Redis. It isn't very clean code right now but will get worked on for the next couple releases.
|
||||
- Ignorelists now exist, and have a Redis state storage [golang module](internal/statestorage/redis/ignorelist/) for CRUD access. Currently three ignorelists are defined in the [config file](config/matchmaker_config.json) with their respective parameters. These are implemented as [Sorted Sets in Redis](https://redis.io/commands#sorted_set).
|
||||
- For those who only want to stand up Open Match and aren't interested in individually tweaking the required kubernetes resources, there are now [three YAML files](install/yaml) that can be used to install Redis, install Open Match, and (optionally) install Prometheus. You'll still need the `sed` [instructions from the Developer Guide](docs/development.md#running-open-match-in-a-development-environment) to substitute in the name of your Docker container registry.
|
||||
- A super-simple module has been created for doing instersections, unions, and differences of lists of player IDs. It lives in `internal/set/set.go`.
|
||||
|
||||
|
||||
### Roadmap
|
||||
- It has become clear from talking to multiple users that the software they write to talk to the Backend API needs a name. 'Backend API Client' is technically correct, but given how many APIs are in Open Match and the overwhelming use of 'Client' to refer to a Game Client in the industry, we're currently calling this a 'Director', as its primary purpose is to 'direct' which profiles are sent to the backend, and 'direct' the resulting MatchObjects to game servers. Further discussion / suggestions are welcome.
|
||||
- We'll be entering the design stage on longer-running MMFs before the end of the year. We'll get a proposal together and on the github repo as a request for comments, so please keep your eye out for that.
|
||||
- Match profiles providing multiple MMFs to run isn't planned anymore. Just send multiple copies of the profile with different MMFs specified via the backendapi.
|
||||
- Redis Sentinel will likely not be supported. Instead, replicated instances and HAProxy may be the HA solution of choice. There's an [outstanding issue to investigate and implement](https://github.com/GoogleCloudPlatform/open-match/issues/41) if it fills our needs, feel free to contribute!
|
||||
|
||||
## v0.1.0 (alpha)
|
||||
Initial release.
|
@ -2,7 +2,6 @@
|
||||
FROM golang:1.10.3 as builder
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match
|
||||
COPY cmd/backendapi cmd/backendapi
|
||||
COPY api/protobuf-spec/backend.pb.go cmd/backendapi/proto/
|
||||
COPY config config
|
||||
COPY internal internal
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/cmd/backendapi
|
||||
|
7
Dockerfile.base
Normal file
7
Dockerfile.base
Normal file
@ -0,0 +1,7 @@
|
||||
# Golang application builder steps
|
||||
FROM golang:1.10.3 as builder
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match
|
||||
COPY config config
|
||||
COPY internal internal
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/internal
|
||||
RUN go get -d -v ...
|
@ -3,6 +3,7 @@ FROM golang:1.10.3 as builder
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match
|
||||
COPY examples/evaluators/golang/simple examples/evaluators/golang/simple
|
||||
COPY config config
|
||||
COPY internal internal
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/examples/evaluators/golang/simple
|
||||
RUN go get -d -v
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
|
||||
|
@ -2,7 +2,6 @@
|
||||
FROM golang:1.10.3 as builder
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match
|
||||
COPY cmd/frontendapi cmd/frontendapi
|
||||
COPY api/protobuf-spec/frontend.pb.go cmd/frontendapi/proto/
|
||||
COPY config config
|
||||
COPY internal internal
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/cmd/frontendapi
|
||||
|
@ -1,10 +1,9 @@
|
||||
# Golang application builder steps
|
||||
FROM golang:1.10.3 as builder
|
||||
# FROM golang:1.10.3 as builder
|
||||
FROM gcr.io/matchmaker-dev-201405/openmatch-devbase as builder
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match
|
||||
COPY examples/functions/golang/simple examples/functions/golang/simple
|
||||
COPY config config
|
||||
COPY internal/statestorage internal/statestorage
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/examples/functions/golang/simple
|
||||
COPY examples/functions/golang/manual-simple examples/functions/golang/manual-simple
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/examples/functions/golang/manual-simple
|
||||
RUN go get -d -v
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o mmf .
|
||||
|
21
Dockerfile.mmf_php
Normal file
21
Dockerfile.mmf_php
Normal file
@ -0,0 +1,21 @@
|
||||
FROM php:7.2-cli
|
||||
|
||||
RUN apt-get update && apt-get install -y -q zip unzip zlib1g-dev && apt-get clean
|
||||
|
||||
RUN cd /usr/local/bin && curl -sS https://getcomposer.org/installer | php
|
||||
RUN cd /usr/local/bin && mv composer.phar composer
|
||||
|
||||
RUN pecl install grpc
|
||||
RUN echo "extension=grpc.so" > /usr/local/etc/php/conf.d/30-grpc.ini
|
||||
|
||||
RUN pecl install protobuf
|
||||
RUN echo "extension=protobuf.so" > /usr/local/etc/php/conf.d/30-protobuf.ini
|
||||
|
||||
WORKDIR /usr/src/open-match
|
||||
COPY examples/functions/php/mmlogic-simple examples/functions/php/mmlogic-simple
|
||||
COPY config config
|
||||
WORKDIR /usr/src/open-match/examples/functions/php/mmlogic-simple
|
||||
|
||||
RUN composer install
|
||||
|
||||
CMD [ "php", "./harness.php" ]
|
9
Dockerfile.mmf_py3
Normal file
9
Dockerfile.mmf_py3
Normal file
@ -0,0 +1,9 @@
|
||||
# Golang application builder steps
|
||||
FROM python:3.5.3 as builder
|
||||
WORKDIR /usr/src/open-match
|
||||
COPY examples/functions/python3/mmlogic-simple examples/functions/python3/mmlogic-simple
|
||||
COPY config config
|
||||
WORKDIR /usr/src/open-match/examples/functions/python3/mmlogic-simple
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
CMD ["python", "./harness.py"]
|
13
Dockerfile.mmlogicapi
Normal file
13
Dockerfile.mmlogicapi
Normal file
@ -0,0 +1,13 @@
|
||||
# Golang application builder steps
|
||||
FROM golang:1.10.3 as builder
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match
|
||||
COPY cmd/mmlogicapi cmd/mmlogicapi
|
||||
COPY config config
|
||||
COPY internal internal
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/cmd/mmlogicapi
|
||||
RUN go get -d -v
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
|
||||
|
||||
#FROM scratch
|
||||
#COPY --from=builder /go/src/github.com/GoogleCloudPlatform/open-match/cmd/frontendapi/frontendapi .
|
||||
ENTRYPOINT ["./mmlogicapi"]
|
116
README.md
116
README.md
@ -1,16 +1,18 @@
|
||||
# Open Match
|
||||
|
||||
Open Match is an open source game matchmaker designed to allow game creators to re-use a common matchmaker framework. It’s designed to be flexible (run it anywhere Kubernetes runs), extensible (match logic can be customized to work for any game), and scalable.
|
||||
Open Match is an open source game matchmaking framework designed to allow game creators to re-use a common matchmaker framework. It’s designed to be flexible (run it anywhere Kubernetes runs), extensible (match logic can be customized to work for any game), and scalable.
|
||||
|
||||
Matchmaking is a complicated process, and when large player populations are involved, many popular matchmaking approaches touch on significant areas of computer science including graph theory and massively concurrent processing. Open Match is an effort to provide a foundation upon which these difficult problems can be addressed by the wider game development community. As Josh Menke — famous for working on matchmaking for many popular triple-A franchises — put it:
|
||||
|
||||
["Matchmaking, a lot of it actually really is just really good engineering. There's a lot of really hard networking and plumbing problems that need to be solved, depending on the size of your audience."](https://youtu.be/-pglxege-gU?t=830)
|
||||
|
||||
|
||||
This project attempts to solve the networking and plumbing problems, so game developers can focus on the logic to match players into great games.
|
||||
|
||||
## Disclaimer
|
||||
This software is currently alpha, and subject to change. **It is not yet ready to be used in production.**
|
||||
This software is currently alpha, and subject to change. Although Open Match has already been used to run [production workloads within Google](https://cloud.google.com/blog/topics/inside-google-cloud/no-tricks-just-treats-globally-scaling-the-halloween-multiplayer-doodle-with-open-match-on-google-cloud), but it's still early days on the way to our final goal. There's plenty left to write and we welcome contributions. **We strongly encourage you to engage with the community through the [Slack or Mailing lists](#get-involved) if you're considering using Open Match in production before the 1.0 release, as the documentation is likely to lag behind the latest version a bit while we focus on getting out of alpha/beta as soon as possible.**
|
||||
|
||||
## Version
|
||||
[The current stable version in master is 0.2.0 (alpha)](https://github.com/GoogleCloudPlatform/open-match/releases/tag/020).
|
||||
|
||||
# Core Concepts
|
||||
|
||||
@ -24,10 +26,11 @@ Open Match is designed to support massively concurrent matchmaking, and to be sc
|
||||
* **Component** — One of the discrete processes in an Open Match deployment. Open Match is composed of multiple scalable microservices called 'components'.
|
||||
* **Roster** — A list of all the players in a match.
|
||||
* **Profile** — The json blob containing all the parameters used to select which players go into a roster.
|
||||
* **Match Object** — A json blob to contain the results of the matchmaking function. Sent with an empty roster section to the backend API from your game backend and then returned with the matchmaking results filled in.
|
||||
* **Match Object** — A protobuffer message format that contains the Profile and the results of the matchmaking function. Sent to the backend API from your game backend with an empty roster and then returned from your MMF with the matchmaking results filled in.
|
||||
* **MMFOrc** — Matchmaker function orchestrator. This Open Match core component is in charge of kicking off custom matchmaking functions (MMFs) and evaluator processes.
|
||||
* **State Storage** — The storage software used by Open Match to hold all the matchmaking state. Open Match ships with [Redis](https://redis.io/) as the default state storage.
|
||||
* **Assignment** — Refers to assigning a player or group of players to a dedicated game server instance. Open Match offers a path to send dedicated game server connection details from your backend to your game clients after a match has been made.
|
||||
* **DGS** — Dedicated game server
|
||||
|
||||
## Requirements
|
||||
* [Kubernetes](https://kubernetes.io/) cluster — tested with version 1.9.
|
||||
@ -41,11 +44,12 @@ Open Match is a set of processes designed to run on Kubernetes. It contains thes
|
||||
1. Frontend API
|
||||
1. Backend API
|
||||
1. Matchmaker Function Orchestrator (MMFOrc)
|
||||
1. Matchmaking Logic (MMLogic) API
|
||||
|
||||
It also explicitly depends on these two **customizable** components.
|
||||
|
||||
1. Matchmaking "Function" (MMF)
|
||||
1. Evaluator
|
||||
1. Evaluator (may be deprecated in future versions)
|
||||
|
||||
While **core** components are fully open source and *can* be modified, they are designed to support the majority of matchmaking scenarios *without need to change the source code*. The Open Match repository ships with simple **customizable** example MMF and Evaluator processes, but it is expected that most users will want full control over the logic in these, so they have been designed to be as easy to modify or replace as possible.
|
||||
|
||||
@ -69,12 +73,27 @@ The Backend API is a server application that implements the [gRPC](https://grpc.
|
||||
|
||||
Your game backend is expected to maintain a connection, waiting for 'filled' match objects containing a roster of players. The Backend API also provides a return path for your game backend to return dedicated game server connection details (an 'assignment') to the game client, and to delete these 'assignments'.
|
||||
|
||||
|
||||
### Matchmaking Function Orchestrator (MMFOrc)
|
||||
|
||||
The MMFOrc kicks off your custom matchmaking function (MMF) for every profile submitted to the Backend API. It also runs the Evaluator to resolve conflicts in case more than one of your profiles matched the same players.
|
||||
|
||||
The MMFOrc exists to orchestrate/schedule your **custom components**, running them as often as required to meet the demands of your game. MMFOrc runs in an endless loop, submitting MMFs and Evaluator jobs to Kubernetes.
|
||||
|
||||
### Matchmaking Logic (MMLogic) API
|
||||
|
||||
The MMLogic API provides a series of gRPC functions that act as a Matchmaking Function SDK. Much of the basic, boilerplate code for an MMF is the same regardless of what players you want to match together. The MMLogic API offers a gRPC interface for many common MMF tasks, such as:
|
||||
|
||||
1. Reading a profile from state storage.
|
||||
1. Running filters on players in state strorage.
|
||||
1. Removing chosen players from consideration by other MMFs (by adding them to an ignore list).
|
||||
1. Writing the matchmaking results to state storage.
|
||||
1. (Optional, NYI) Exporting MMF stats for metrics collection.
|
||||
|
||||
More details about the available gRPC calls can be found in the [API Specification](api/protobuf-spec/messages.proto).
|
||||
|
||||
**Note**: using the MMLogic API is **optional**. It tries to simplify the development of MMFs, but if you want to take care of these tasks on your own, you can make few or no calls to the MMLogic API as long as your MMF still completes all the required tasks. Read the [Matchmaking Functions section](#matchmaking-functions-mmfs) for more details of what work an MMF must do.
|
||||
|
||||
### Evaluator
|
||||
|
||||
The Evaluator resolves conflicts when multiple matches want to include the same player(s).
|
||||
@ -87,16 +106,23 @@ Large-scale concurrent matchmaking functions is a complex topic, and users who w
|
||||
|
||||
Matchmaking Functions (MMFs) are run by the Matchmaker Function Orchestrator (MMFOrc) — once per profile it sees in state storage. The MMF is run as a Job in Kubernetes, and has full access to read and write from state storage. At a high level, the encouraged pattern is to write a MMF in whatever language you are comfortable in that can do the following things:
|
||||
|
||||
1. Read/write from the Open Match state storage — Open Match ships with Redis as the default state storage.
|
||||
1. Be packaged in a (Linux) Docker container.
|
||||
1. Read a profile you wrote to state storage using the Backend API.
|
||||
1. Select from the player data you wrote to state storage using the Frontend API.
|
||||
1. Run your custom logic to try to find a match.
|
||||
1. Write the match object it creates to state storage at a specified key.
|
||||
1. Remove the players it selected from consideration by other MMFs.
|
||||
1. (Optional, but recommended) Export stats for metrics collection.
|
||||
- [x] Be packaged in a (Linux) Docker container.
|
||||
- [x] Read/write from the Open Match state storage — Open Match ships with Redis as the default state storage.
|
||||
- [x] Read a profile you wrote to state storage using the Backend API.
|
||||
- [x] Select from the player data you wrote to state storage using the Frontend API.
|
||||
- [ ] Run your custom logic to try to find a match.
|
||||
- [x] Write the match object it creates to state storage at a specified key.
|
||||
- [x] Remove the players it selected from consideration by other MMFs.
|
||||
- [x] Notify the MMFOrc of completion.
|
||||
- [x] (Optional, but recommended) Export stats for metrics collection.
|
||||
|
||||
Example MMFs are provided in Golang and C#.
|
||||
**Open Match offers [matchmaking logic API](#matchmaking-logic-mmlogic-api) calls for handling the checked items, as long as you are able to format your input and output in the data schema Open Match expects (defined in the [protobuf messages](api/protobuf-spec/messages.proto)).** You can to do this work yourself if you don't want to or can't use the data schema Open Match is looking for. However, the data formats expected by Open Match are pretty generalized and will work with most common matchmaking scenarios and game types. If you have questions about how to fit your data into the formats specified, feel free to ask us in the [Slack or mailing group](#get-involved).
|
||||
|
||||
Example MMFs are provided in these languages:
|
||||
- [C#](examples/functions/csharp/simple) (doesn't use the MMLogic API)
|
||||
- [Python3](examples/functions/python3/mmlogic-simple) (MMLogic API enabled)
|
||||
- [PHP](examples/functions/php/mmlogic-simple) (MMLogic API enabled)
|
||||
- [golang](examples/functions/golang/manual-simple) (doesn't use the MMLogic API)
|
||||
|
||||
## Open Source Software integrations
|
||||
|
||||
@ -129,17 +155,17 @@ The following examples of how to call the APIs are provided in the repository. B
|
||||
|
||||
Documentation and usage guides on how to set up and customize Open Match.
|
||||
|
||||
## Precompiled container images
|
||||
### Precompiled container images
|
||||
|
||||
Once we reach a 1.0 release, we plan to produce publicly available (Linux) Docker container images of major releases in a public image registry. Until then, refer to the 'Compiling from source' section below.
|
||||
|
||||
## Compiling from source
|
||||
### Compiling from source
|
||||
|
||||
All components of Open Match produce (Linux) Docker container images as artifacts, and there are included `Dockerfile`s for each. [Google Cloud Platform Cloud Build](https://cloud.google.com/cloud-build/docs/) users will also find `cloudbuild_COMPONENT.yaml` files for each component in the repository root.
|
||||
|
||||
All the core components for Open Match are written in Golang and use the [Dockerfile multistage builder pattern](https://docs.docker.com/develop/develop-images/multistage-build/). This pattern uses intermediate Docker containers as a Golang build environment while producing lightweight, minimized container images as final build artifacts. When the project is ready for production, we will modify the `Dockerfile`s to uncomment the last build stage. Although this pattern is great for production container images, it removes most of the utilities required to troubleshoot issues during development.
|
||||
|
||||
## Configuration
|
||||
### Configuration
|
||||
|
||||
Currently, each component reads a local config file `matchmaker_config.json`, and all components assume they have the same configuration. To this end, there is a single centralized config file located in the `<REPO_ROOT>/config/` which is symlinked to each component's subdirectory for convenience when building locally. When `docker build`ing the component container images, the Dockerfile copies the centralized config file into the component directory.
|
||||
|
||||
@ -187,28 +213,40 @@ Open Match is in active development - we would love your help in shaping its fut
|
||||
|
||||
Apache 2.0
|
||||
|
||||
# Missing functionality
|
||||
|
||||
* Player/Group records generated when a client enters the matchmaking pool need to be removed after a certain amount of time with no activity. When using Redis, this will be implemented as a expiration on the player record.
|
||||
* Instrumentation of MMFs is in the planning stages. Since MMFs are by design meant to be completely customizable (to the point of allowing any process that can be packaged in a Docker container), metrics/stats will need to have an expected format and formalized outgoing pathway. Currently the thought is that it might be that the metrics should be written to a particular key in statestorage in a format compatible with opencensus, and will be collected, aggreggated, and exported to Prometheus using another process.
|
||||
* The Kubernetes service account used by the MMFOrc should be updated to have min required permissions.
|
||||
* Autoscaling isn't turned on for the Frontend or Backend API Kubernetes deployments by default.
|
||||
* Match profiles should be able to define multiple MMF container images to run, but this is not currently supported. This enables A/B testing and several other scenarios.
|
||||
* Out-of-the-box, the Redis deployment should be a HA configuration using [Redis Sentinel](https://redis.io/topics/sentinel).
|
||||
* Redis watch should be unified to watch a hash and stream updates. The code for this is written and validated but not committed yet. We don't want to support two redis watcher code paths, so the backend watch of the match object should be switched to unify the way the frontend and backend watch keys. Unfortunately this change touches the whole chain of components that touch backend match objects (mmf, evaluator, backendapi) and so needs additional work and testing before it is integrated.
|
||||
|
||||
# Planned improvements
|
||||
|
||||
* “Writing your first matchmaker” getting started guide will be included in an upcoming version.
|
||||
* Documentation for using the example customizable components and the `backendstub` and `frontendstub` applications to do an end-to-end (e2e) test will be written. This all works now, but needs to be written up.
|
||||
* A [Helm](https://helm.sh/) chart to stand up Open Match will be provided in an upcoming version.
|
||||
* We plan to host 'official' docker images for all release versions of the core components in publicly available docker registries soon.
|
||||
* CI/CD for this repo and the associated status tags are planned.
|
||||
* Documentation on release process and release calendar.
|
||||
* [OpenCensus tracing](https://opencensus.io/core-concepts/tracing/) will be implemented in an upcoming version.
|
||||
* Read logrus logging configuration from matchmaker_config.json.
|
||||
* Golang unit tests will be shipped in an upcoming version.
|
||||
* A full load-testing and e2e testing suite will be included in an upcoming version.
|
||||
* All state storage operations should be isolated from core components into the `statestorage/` modules. This is necessary precursor work to enabling Open Match state storage to use software other than Redis.
|
||||
* The MMFOrc component name will be updated in a future version to something easier to understand. Suggestions welcome!
|
||||
* The MMFOrc component currently requires a default service account with permission to kick of k8s jobs, but the revision today makes the service account have full permissions. This needs to be reworked to have min required RBAC permissions before it is used in production, but is fine for closed testing and development.
|
||||
## Documentation
|
||||
- [ ] “Writing your first matchmaker” getting started guide will be included in an upcoming version.
|
||||
- [ ] Documentation for using the example customizable components and the `backendstub` and `frontendstub` applications to do an end-to-end (e2e) test will be written. This all works now, but needs to be written up.
|
||||
- [ ] Documentation on release process and release calendar.
|
||||
|
||||
## State storage
|
||||
- [ ] All state storage operations should be isolated from core components into the `statestorage/` modules. This is necessary precursor work to enabling Open Match state storage to use software other than Redis.
|
||||
- [ ] [The Redis deployment should have an example HA configuration](https://github.com/GoogleCloudPlatform/open-match/issues/41)
|
||||
- [ ] Redis watch should be unified to watch a hash and stream updates. The code for this is written and validated but not committed yet. We don't want to support two redis watcher code paths, so the backend watch of the match object should be switched to unify the way the frontend and backend watch keys. The backend part of this is in but the frontend part is in another branch and will be committed later.
|
||||
- [ ] Player/Group records generated when a client enters the matchmaking pool need to be removed after a certain amount of time with no activity. When using Redis, this will be implemented as a expiration on the player record.
|
||||
|
||||
## Instrumentation / Metrics / Analytics
|
||||
- [ ] Instrumentation of MMFs is in the planning stages. Since MMFs are by design meant to be completely customizable (to the point of allowing any process that can be packaged in a Docker container), metrics/stats will need to have an expected format and formalized outgoing pathway. Currently the thought is that it might be that the metrics should be written to a particular key in statestorage in a format compatible with opencensus, and will be collected, aggreggated, and exported to Prometheus using another process.
|
||||
- [ ] [OpenCensus tracing](https://opencensus.io/core-concepts/tracing/) will be implemented in an upcoming version.
|
||||
- [ ] Read logrus logging configuration from matchmaker_config.json.
|
||||
|
||||
## Security
|
||||
- [ ] The Kubernetes service account used by the MMFOrc should be updated to have min required permissions.
|
||||
|
||||
## Kubernetes
|
||||
- [ ] Autoscaling isn't turned on for the Frontend or Backend API Kubernetes deployments by default.
|
||||
- [ ] A [Helm](https://helm.sh/) chart to stand up Open Match will be provided in an upcoming version. For now just use the [installation YAMLs](./install/yaml).
|
||||
|
||||
## CI / CD / Build
|
||||
- [ ] We plan to host 'official' docker images for all release versions of the core components in publicly available docker registries soon.
|
||||
- [ ] CI/CD for this repo and the associated status tags are planned.
|
||||
- [ ] Golang unit tests will be shipped in an upcoming version.
|
||||
- [ ] A full load-testing and e2e testing suite will be included in an upcoming version.
|
||||
|
||||
## Will not Implement
|
||||
- [X] Defining multiple images inside a profile for the purposes of experimentation adds another layer of complexity into profiles that can instead be handled outside of open match with custom match functions in collaboration with a director (thing that calls backend to schedule matchmaking)
|
||||
|
||||
### Special Thanks
|
||||
- Thanks to https://jbt.github.io/markdown-editor/ for help in marking this document down.
|
||||
|
13
api/protobuf-spec/LICENSE
Normal file
13
api/protobuf-spec/LICENSE
Normal file
@ -0,0 +1,13 @@
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
17
api/protobuf-spec/README.md
Normal file
17
api/protobuf-spec/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
** REST compatibility
|
||||
Follow the guidelines at https://cloud.google.com/endpoints/docs/grpc/transcoding
|
||||
to keep the gRPC service definitions friendly to REST transcoding. An excerpt:
|
||||
|
||||
"Transcoding involves mapping HTTP/JSON requests and their parameters to gRPC
|
||||
methods and their parameters and return types (we'll look at exactly how you
|
||||
do this in the following sections). Because of this, while it's possible to
|
||||
map an HTTP/JSON request to any arbitrary API method, it's simplest and most
|
||||
intuitive to do so if the gRPC API itself is structured in a
|
||||
resource-oriented way, just like a traditional HTTP REST API. In other
|
||||
words, the API service should be designed so that it uses a small number of
|
||||
standard methods (corresponding to HTTP verbs like GET, PUT, and so on) that
|
||||
operate on the service's resources (and collections of resources, which are
|
||||
themselves a type of resource).
|
||||
These standard methods are List, Get, Create, Update, and Delete."
|
||||
|
||||
It is for these reasons we don't have gRPC calls that support bi-directional streaming in Open Match.
|
@ -1,482 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 Google LLC
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: backend.proto
|
||||
|
||||
/*
|
||||
Package backend is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
backend.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Profile
|
||||
MatchObject
|
||||
Result
|
||||
Roster
|
||||
ConnectionInfo
|
||||
Assignments
|
||||
*/
|
||||
package backend
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// Data structure for a profile to pass to the matchmaking function.
|
||||
type Profile struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
Properties string `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Profile) Reset() { *m = Profile{} }
|
||||
func (m *Profile) String() string { return proto.CompactTextString(m) }
|
||||
func (*Profile) ProtoMessage() {}
|
||||
func (*Profile) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *Profile) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Profile) GetProperties() string {
|
||||
if m != nil {
|
||||
return m.Properties
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Data structure for all the properties of a match.
|
||||
type MatchObject struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
Properties string `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
func (m *MatchObject) Reset() { *m = MatchObject{} }
|
||||
func (m *MatchObject) String() string { return proto.CompactTextString(m) }
|
||||
func (*MatchObject) ProtoMessage() {}
|
||||
func (*MatchObject) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func (m *MatchObject) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *MatchObject) GetProperties() string {
|
||||
if m != nil {
|
||||
return m.Properties
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Simple message to return success/failure and error status.
|
||||
type Result struct {
|
||||
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
|
||||
Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Result) Reset() { *m = Result{} }
|
||||
func (m *Result) String() string { return proto.CompactTextString(m) }
|
||||
func (*Result) ProtoMessage() {}
|
||||
func (*Result) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
||||
|
||||
func (m *Result) GetSuccess() bool {
|
||||
if m != nil {
|
||||
return m.Success
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Result) GetError() string {
|
||||
if m != nil {
|
||||
return m.Error
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Data structure to hold a list of players in a match.
|
||||
type Roster struct {
|
||||
PlayerIds string `protobuf:"bytes,1,opt,name=player_ids,json=playerIds" json:"player_ids,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Roster) Reset() { *m = Roster{} }
|
||||
func (m *Roster) String() string { return proto.CompactTextString(m) }
|
||||
func (*Roster) ProtoMessage() {}
|
||||
func (*Roster) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
|
||||
|
||||
func (m *Roster) GetPlayerIds() string {
|
||||
if m != nil {
|
||||
return m.PlayerIds
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Simple message used to pass the connection string for the DGS to the player.
|
||||
type ConnectionInfo struct {
|
||||
ConnectionString string `protobuf:"bytes,1,opt,name=connection_string,json=connectionString" json:"connection_string,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ConnectionInfo) Reset() { *m = ConnectionInfo{} }
|
||||
func (m *ConnectionInfo) String() string { return proto.CompactTextString(m) }
|
||||
func (*ConnectionInfo) ProtoMessage() {}
|
||||
func (*ConnectionInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
|
||||
|
||||
func (m *ConnectionInfo) GetConnectionString() string {
|
||||
if m != nil {
|
||||
return m.ConnectionString
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Assignments struct {
|
||||
Roster *Roster `protobuf:"bytes,1,opt,name=roster" json:"roster,omitempty"`
|
||||
ConnectionInfo *ConnectionInfo `protobuf:"bytes,2,opt,name=connection_info,json=connectionInfo" json:"connection_info,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Assignments) Reset() { *m = Assignments{} }
|
||||
func (m *Assignments) String() string { return proto.CompactTextString(m) }
|
||||
func (*Assignments) ProtoMessage() {}
|
||||
func (*Assignments) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
|
||||
|
||||
func (m *Assignments) GetRoster() *Roster {
|
||||
if m != nil {
|
||||
return m.Roster
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Assignments) GetConnectionInfo() *ConnectionInfo {
|
||||
if m != nil {
|
||||
return m.ConnectionInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Profile)(nil), "Profile")
|
||||
proto.RegisterType((*MatchObject)(nil), "MatchObject")
|
||||
proto.RegisterType((*Result)(nil), "Result")
|
||||
proto.RegisterType((*Roster)(nil), "Roster")
|
||||
proto.RegisterType((*ConnectionInfo)(nil), "ConnectionInfo")
|
||||
proto.RegisterType((*Assignments)(nil), "Assignments")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for API service
|
||||
|
||||
type APIClient interface {
|
||||
// Calls to ask the matchmaker to run a matchmaking function.
|
||||
//
|
||||
// Run MMF once. Return a matchobject that fits this profile.
|
||||
CreateMatch(ctx context.Context, in *Profile, opts ...grpc.CallOption) (*MatchObject, error)
|
||||
// Continually run MMF and stream matchobjects that fit this profile until
|
||||
// client closes the connection.
|
||||
ListMatches(ctx context.Context, in *Profile, opts ...grpc.CallOption) (API_ListMatchesClient, error)
|
||||
// Delete a matchobject from state storage manually. (Matchobjects in state
|
||||
// storage will also automatically expire after a while)
|
||||
DeleteMatch(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*Result, error)
|
||||
// Call that manage communication of DGS connection info to players.
|
||||
//
|
||||
// Write the DGS connection info for the list of players in the
|
||||
// Assignments.roster to state storage, so that info can be read by the game
|
||||
// client(s).
|
||||
CreateAssignments(ctx context.Context, in *Assignments, opts ...grpc.CallOption) (*Result, error)
|
||||
// Remove DGS connection info for the list of players in the Roster from
|
||||
// state storage.
|
||||
DeleteAssignments(ctx context.Context, in *Roster, opts ...grpc.CallOption) (*Result, error)
|
||||
}
|
||||
|
||||
type aPIClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewAPIClient(cc *grpc.ClientConn) APIClient {
|
||||
return &aPIClient{cc}
|
||||
}
|
||||
|
||||
func (c *aPIClient) CreateMatch(ctx context.Context, in *Profile, opts ...grpc.CallOption) (*MatchObject, error) {
|
||||
out := new(MatchObject)
|
||||
err := grpc.Invoke(ctx, "/API/CreateMatch", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *aPIClient) ListMatches(ctx context.Context, in *Profile, opts ...grpc.CallOption) (API_ListMatchesClient, error) {
|
||||
stream, err := grpc.NewClientStream(ctx, &_API_serviceDesc.Streams[0], c.cc, "/API/ListMatches", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &aPIListMatchesClient{stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type API_ListMatchesClient interface {
|
||||
Recv() (*MatchObject, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type aPIListMatchesClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *aPIListMatchesClient) Recv() (*MatchObject, error) {
|
||||
m := new(MatchObject)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *aPIClient) DeleteMatch(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/API/DeleteMatch", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *aPIClient) CreateAssignments(ctx context.Context, in *Assignments, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/API/CreateAssignments", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *aPIClient) DeleteAssignments(ctx context.Context, in *Roster, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/API/DeleteAssignments", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for API service
|
||||
|
||||
type APIServer interface {
|
||||
// Calls to ask the matchmaker to run a matchmaking function.
|
||||
//
|
||||
// Run MMF once. Return a matchobject that fits this profile.
|
||||
CreateMatch(context.Context, *Profile) (*MatchObject, error)
|
||||
// Continually run MMF and stream matchobjects that fit this profile until
|
||||
// client closes the connection.
|
||||
ListMatches(*Profile, API_ListMatchesServer) error
|
||||
// Delete a matchobject from state storage manually. (Matchobjects in state
|
||||
// storage will also automatically expire after a while)
|
||||
DeleteMatch(context.Context, *MatchObject) (*Result, error)
|
||||
// Call that manage communication of DGS connection info to players.
|
||||
//
|
||||
// Write the DGS connection info for the list of players in the
|
||||
// Assignments.roster to state storage, so that info can be read by the game
|
||||
// client(s).
|
||||
CreateAssignments(context.Context, *Assignments) (*Result, error)
|
||||
// Remove DGS connection info for the list of players in the Roster from
|
||||
// state storage.
|
||||
DeleteAssignments(context.Context, *Roster) (*Result, error)
|
||||
}
|
||||
|
||||
func RegisterAPIServer(s *grpc.Server, srv APIServer) {
|
||||
s.RegisterService(&_API_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _API_CreateMatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Profile)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(APIServer).CreateMatch(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/API/CreateMatch",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(APIServer).CreateMatch(ctx, req.(*Profile))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _API_ListMatches_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(Profile)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(APIServer).ListMatches(m, &aPIListMatchesServer{stream})
|
||||
}
|
||||
|
||||
type API_ListMatchesServer interface {
|
||||
Send(*MatchObject) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type aPIListMatchesServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *aPIListMatchesServer) Send(m *MatchObject) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _API_DeleteMatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MatchObject)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(APIServer).DeleteMatch(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/API/DeleteMatch",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(APIServer).DeleteMatch(ctx, req.(*MatchObject))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _API_CreateAssignments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Assignments)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(APIServer).CreateAssignments(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/API/CreateAssignments",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(APIServer).CreateAssignments(ctx, req.(*Assignments))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _API_DeleteAssignments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Roster)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(APIServer).DeleteAssignments(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/API/DeleteAssignments",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(APIServer).DeleteAssignments(ctx, req.(*Roster))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _API_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "API",
|
||||
HandlerType: (*APIServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "CreateMatch",
|
||||
Handler: _API_CreateMatch_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteMatch",
|
||||
Handler: _API_DeleteMatch_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CreateAssignments",
|
||||
Handler: _API_CreateAssignments_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteAssignments",
|
||||
Handler: _API_DeleteAssignments_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "ListMatches",
|
||||
Handler: _API_ListMatches_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "backend.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("backend.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 344 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xcf, 0x4e, 0xc2, 0x40,
|
||||
0x10, 0xc6, 0x29, 0xc6, 0x16, 0x66, 0x11, 0x64, 0xe3, 0x81, 0x90, 0xf8, 0x27, 0x3d, 0x88, 0x46,
|
||||
0xb3, 0x31, 0x78, 0xc1, 0x83, 0x07, 0x82, 0x17, 0x12, 0x8d, 0xa4, 0x3e, 0x00, 0x29, 0xdb, 0x01,
|
||||
0x56, 0xeb, 0x6e, 0xb3, 0xbb, 0x1c, 0x7c, 0x53, 0x1f, 0xc7, 0xb8, 0x2d, 0xba, 0x1c, 0x3c, 0x78,
|
||||
0x9c, 0x5f, 0xbf, 0x6f, 0xe6, 0xeb, 0xcc, 0xc2, 0xc1, 0x22, 0xe5, 0x6f, 0x28, 0x33, 0x56, 0x68,
|
||||
0x65, 0x55, 0x7c, 0x07, 0xd1, 0x4c, 0xab, 0xa5, 0xc8, 0x91, 0xb6, 0xa1, 0x2e, 0xb2, 0x5e, 0x70,
|
||||
0x16, 0x5c, 0x34, 0x93, 0xba, 0xc8, 0xe8, 0x09, 0x40, 0xa1, 0x55, 0x81, 0xda, 0x0a, 0x34, 0xbd,
|
||||
0xba, 0xe3, 0x1e, 0x89, 0xef, 0x81, 0x3c, 0xa5, 0x96, 0xaf, 0x9f, 0x17, 0xaf, 0xc8, 0xed, 0xbf,
|
||||
0xed, 0x23, 0x08, 0x13, 0x34, 0x9b, 0xdc, 0xd2, 0x1e, 0x44, 0x66, 0xc3, 0x39, 0x1a, 0xe3, 0xec,
|
||||
0x8d, 0x64, 0x5b, 0xd2, 0x23, 0xd8, 0x47, 0xad, 0x95, 0xae, 0xec, 0x65, 0x11, 0x0f, 0x20, 0x4c,
|
||||
0x94, 0xb1, 0xa8, 0xe9, 0x31, 0x40, 0x91, 0xa7, 0x1f, 0xa8, 0xe7, 0x22, 0x33, 0xd5, 0xec, 0x66,
|
||||
0x49, 0xa6, 0xd9, 0x77, 0xc2, 0xf6, 0x44, 0x49, 0x89, 0xdc, 0x0a, 0x25, 0xa7, 0x72, 0xa9, 0xe8,
|
||||
0x15, 0x74, 0xf9, 0x0f, 0x99, 0x1b, 0xab, 0x85, 0x5c, 0x55, 0xbe, 0xc3, 0xdf, 0x0f, 0x2f, 0x8e,
|
||||
0xc7, 0x6b, 0x20, 0x63, 0x63, 0xc4, 0x4a, 0xbe, 0xa3, 0xb4, 0x86, 0x9e, 0x42, 0xa8, 0xdd, 0x58,
|
||||
0x67, 0x20, 0xc3, 0x88, 0x95, 0x29, 0x92, 0x0a, 0xd3, 0x11, 0x74, 0xbc, 0xe6, 0x42, 0x2e, 0x95,
|
||||
0xcb, 0x4d, 0x86, 0x1d, 0xb6, 0x1b, 0x23, 0x69, 0xf3, 0x9d, 0x7a, 0xf8, 0x19, 0xc0, 0xde, 0x78,
|
||||
0x36, 0xa5, 0x03, 0x20, 0x13, 0x8d, 0xa9, 0x45, 0xb7, 0x58, 0xda, 0x60, 0xd5, 0x6d, 0xfa, 0x2d,
|
||||
0xe6, 0xad, 0x3a, 0xae, 0xd1, 0x4b, 0x20, 0x8f, 0xc2, 0x58, 0x07, 0xd1, 0xfc, 0x2d, 0xbc, 0x09,
|
||||
0xe8, 0x39, 0x90, 0x07, 0xcc, 0x71, 0xdb, 0x73, 0x47, 0xd0, 0x8f, 0x58, 0x79, 0x83, 0xb8, 0x46,
|
||||
0xaf, 0xa1, 0x5b, 0xce, 0xf6, 0xff, 0xb9, 0xc5, 0xbc, 0xca, 0x57, 0x0f, 0xa0, 0x5b, 0x76, 0xf5,
|
||||
0xd5, 0xdb, 0x8d, 0x78, 0xc2, 0x45, 0xe8, 0xde, 0xd9, 0xed, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xf2, 0x23, 0x14, 0x36, 0x78, 0x02, 0x00, 0x00,
|
||||
}
|
@ -1,76 +1,56 @@
|
||||
// Follow the guidelines at https://cloud.google.com/endpoints/docs/grpc/transcoding
|
||||
// to keep the gRPC service definitions friendly to REST transcoding. An excerpt:
|
||||
//
|
||||
// "Transcoding involves mapping HTTP/JSON requests and their parameters to gRPC
|
||||
// methods and their parameters and return types (we'll look at exactly how you
|
||||
// do this in the following sections). Because of this, while it's possible to
|
||||
// map an HTTP/JSON request to any arbitrary API method, it's simplest and most
|
||||
// intuitive to do so if the gRPC API itself is structured in a
|
||||
// resource-oriented way, just like a traditional HTTP REST API. In other
|
||||
// words, the API service should be designed so that it uses a small number of
|
||||
// standard methods (corresponding to HTTP verbs like GET, PUT, and so on) that
|
||||
// operate on the service's resources (and collections of resources, which are
|
||||
// themselves a type of resource).
|
||||
// These standard methods are List, Get, Create, Update, and Delete."
|
||||
//
|
||||
syntax = 'proto3';
|
||||
package api;
|
||||
option go_package = "github.com/GoogleCloudPlatform/open-match/internal/pb";
|
||||
|
||||
service API {
|
||||
// The protobuf messages sent in the gRPC calls are defined 'messages.proto'.
|
||||
import 'api/protobuf-spec/messages.proto';
|
||||
|
||||
service Backend {
|
||||
// Calls to ask the matchmaker to run a matchmaking function.
|
||||
//
|
||||
|
||||
// Run MMF once. Return a matchobject that fits this profile.
|
||||
rpc CreateMatch(Profile) returns (MatchObject) {}
|
||||
// INPUT: MatchObject message with these fields populated:
|
||||
// - id
|
||||
// - properties
|
||||
// - [optional] roster, any fields you fill are available to your MMF.
|
||||
// - [optional] pools, any fields you fill are available to your MMF.
|
||||
// OUTPUT: MatchObject message with these fields populated:
|
||||
// - id
|
||||
// - properties
|
||||
// - error. Empty if no error was encountered
|
||||
// - rosters, if you choose to fill them in your MMF. (Recommended)
|
||||
// - pools, if you used the MMLogicAPI in your MMF. (Recommended, and provides stats)
|
||||
rpc CreateMatch(messages.MatchObject) returns (messages.MatchObject) {}
|
||||
// Continually run MMF and stream matchobjects that fit this profile until
|
||||
// client closes the connection.
|
||||
rpc ListMatches(Profile) returns (stream MatchObject) {}
|
||||
// client closes the connection. Same inputs/outputs as CreateMatch.
|
||||
rpc ListMatches(messages.MatchObject) returns (stream messages.MatchObject) {}
|
||||
|
||||
// Delete a matchobject from state storage manually. (Matchobjects in state
|
||||
// storage will also automatically expire after a while)
|
||||
rpc DeleteMatch(MatchObject) returns (Result) {}
|
||||
// INPUT: MatchObject message with the 'id' field populated.
|
||||
// (All other fields are ignored.)
|
||||
rpc DeleteMatch(messages.MatchObject) returns (messages.Result) {}
|
||||
|
||||
// Call that manage communication of DGS connection info to players.
|
||||
//
|
||||
// Write the DGS connection info for the list of players in the
|
||||
// Assignments.roster to state storage, so that info can be read by the game
|
||||
// client(s).
|
||||
// TODO: change this to be agnostic; return a 'result' instead of a connection
|
||||
// string so it can be integrated with session service etc
|
||||
rpc CreateAssignments(Assignments) returns (Result) {}
|
||||
// Remove DGS connection info for the list of players in the Roster from
|
||||
// state storage.
|
||||
rpc DeleteAssignments(Roster) returns (Result) {}
|
||||
}
|
||||
// Call fors communication of connection info to players.
|
||||
|
||||
// Data structure for a profile to pass to the matchmaking function.
|
||||
message Profile{
|
||||
string id = 1; // By convention, the CRC32 of the properties string.
|
||||
string properties = 2; // By convention, a JSON-encoded string
|
||||
}
|
||||
|
||||
// Data structure for all the properties of a match.
|
||||
message MatchObject{
|
||||
string id = 1; // By convention, a UUID
|
||||
string properties = 2; // By convention, a JSON-encoded string
|
||||
Roster roster = 3; // NYI
|
||||
}
|
||||
|
||||
// Simple message to return success/failure and error status.
|
||||
message Result{
|
||||
bool success = 1;
|
||||
string error = 2;
|
||||
}
|
||||
|
||||
// Data structure to hold a list of players in a match.
|
||||
message Roster{
|
||||
string player_ids = 1; // By convention, a space-delimited list of player IDs
|
||||
}
|
||||
|
||||
// Simple message used to pass the connection string for the DGS to the player.
|
||||
message ConnectionInfo{
|
||||
string connection_string = 1; // Passed by the matchmaker to game clients without modification.
|
||||
}
|
||||
|
||||
message Assignments{
|
||||
Roster roster = 1;
|
||||
ConnectionInfo connection_info = 2;
|
||||
// Write the connection info for the list of players in the
|
||||
// Assignments.messages.Rosters to state storage. The FrontendAPI is
|
||||
// responsible for sending anything sent here to the game clients.
|
||||
// Sending a player to this function kicks off a process that removes
|
||||
// the player from future matchmaking functions by adding them to the
|
||||
// 'deindexed' player list and then deleting their player ID from state storage
|
||||
// indexes.
|
||||
// INPUT: Assignments message with these fields populated:
|
||||
// - connection_info, anything you write to this string is sent to Frontend API
|
||||
// - rosters. You can send any number of rosters, containing any number of
|
||||
// player messages. All players from all rosters will be sent the connection_info.
|
||||
// The only field in the Player object that is used by CreateAssignments is
|
||||
// the id field. All others are silently ignored.
|
||||
rpc CreateAssignments(messages.Assignments) returns (messages.Result) {}
|
||||
// Remove DGS connection info from state storage for players.
|
||||
// INPUT: Roster message with the 'players' field populated.
|
||||
// The only field in the Player object that is used by
|
||||
// DeleteAssignments is the 'id' field. All others are silently ignored. If
|
||||
// you need to delete multiple rosters, make multiple calls.
|
||||
rpc DeleteAssignments(messages.Roster) returns (messages.Result) {}
|
||||
}
|
||||
|
@ -1,321 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: frontend.proto
|
||||
|
||||
/*
|
||||
Package frontend is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
frontend.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Group
|
||||
PlayerId
|
||||
ConnectionInfo
|
||||
Result
|
||||
*/
|
||||
package frontend
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// Data structure for a group of players to pass to the matchmaking function.
|
||||
// Obviously, the group can be a group of one!
|
||||
type Group struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
Properties string `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Group) Reset() { *m = Group{} }
|
||||
func (m *Group) String() string { return proto.CompactTextString(m) }
|
||||
func (*Group) ProtoMessage() {}
|
||||
func (*Group) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *Group) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Group) GetProperties() string {
|
||||
if m != nil {
|
||||
return m.Properties
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type PlayerId struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PlayerId) Reset() { *m = PlayerId{} }
|
||||
func (m *PlayerId) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlayerId) ProtoMessage() {}
|
||||
func (*PlayerId) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func (m *PlayerId) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Simple message used to pass the connection string for the DGS to the player.
|
||||
type ConnectionInfo struct {
|
||||
ConnectionString string `protobuf:"bytes,1,opt,name=connection_string,json=connectionString" json:"connection_string,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ConnectionInfo) Reset() { *m = ConnectionInfo{} }
|
||||
func (m *ConnectionInfo) String() string { return proto.CompactTextString(m) }
|
||||
func (*ConnectionInfo) ProtoMessage() {}
|
||||
func (*ConnectionInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
||||
|
||||
func (m *ConnectionInfo) GetConnectionString() string {
|
||||
if m != nil {
|
||||
return m.ConnectionString
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Simple message to return success/failure and error status.
|
||||
type Result struct {
|
||||
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
|
||||
Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Result) Reset() { *m = Result{} }
|
||||
func (m *Result) String() string { return proto.CompactTextString(m) }
|
||||
func (*Result) ProtoMessage() {}
|
||||
func (*Result) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
|
||||
|
||||
func (m *Result) GetSuccess() bool {
|
||||
if m != nil {
|
||||
return m.Success
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Result) GetError() string {
|
||||
if m != nil {
|
||||
return m.Error
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Group)(nil), "Group")
|
||||
proto.RegisterType((*PlayerId)(nil), "PlayerId")
|
||||
proto.RegisterType((*ConnectionInfo)(nil), "ConnectionInfo")
|
||||
proto.RegisterType((*Result)(nil), "Result")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for API service
|
||||
|
||||
type APIClient interface {
|
||||
CreateRequest(ctx context.Context, in *Group, opts ...grpc.CallOption) (*Result, error)
|
||||
DeleteRequest(ctx context.Context, in *Group, opts ...grpc.CallOption) (*Result, error)
|
||||
GetAssignment(ctx context.Context, in *PlayerId, opts ...grpc.CallOption) (*ConnectionInfo, error)
|
||||
DeleteAssignment(ctx context.Context, in *PlayerId, opts ...grpc.CallOption) (*Result, error)
|
||||
}
|
||||
|
||||
type aPIClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewAPIClient(cc *grpc.ClientConn) APIClient {
|
||||
return &aPIClient{cc}
|
||||
}
|
||||
|
||||
func (c *aPIClient) CreateRequest(ctx context.Context, in *Group, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/API/CreateRequest", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *aPIClient) DeleteRequest(ctx context.Context, in *Group, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/API/DeleteRequest", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *aPIClient) GetAssignment(ctx context.Context, in *PlayerId, opts ...grpc.CallOption) (*ConnectionInfo, error) {
|
||||
out := new(ConnectionInfo)
|
||||
err := grpc.Invoke(ctx, "/API/GetAssignment", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *aPIClient) DeleteAssignment(ctx context.Context, in *PlayerId, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/API/DeleteAssignment", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for API service
|
||||
|
||||
type APIServer interface {
|
||||
CreateRequest(context.Context, *Group) (*Result, error)
|
||||
DeleteRequest(context.Context, *Group) (*Result, error)
|
||||
GetAssignment(context.Context, *PlayerId) (*ConnectionInfo, error)
|
||||
DeleteAssignment(context.Context, *PlayerId) (*Result, error)
|
||||
}
|
||||
|
||||
func RegisterAPIServer(s *grpc.Server, srv APIServer) {
|
||||
s.RegisterService(&_API_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _API_CreateRequest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Group)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(APIServer).CreateRequest(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/API/CreateRequest",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(APIServer).CreateRequest(ctx, req.(*Group))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _API_DeleteRequest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Group)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(APIServer).DeleteRequest(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/API/DeleteRequest",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(APIServer).DeleteRequest(ctx, req.(*Group))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _API_GetAssignment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PlayerId)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(APIServer).GetAssignment(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/API/GetAssignment",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(APIServer).GetAssignment(ctx, req.(*PlayerId))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _API_DeleteAssignment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PlayerId)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(APIServer).DeleteAssignment(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/API/DeleteAssignment",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(APIServer).DeleteAssignment(ctx, req.(*PlayerId))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _API_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "API",
|
||||
HandlerType: (*APIServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "CreateRequest",
|
||||
Handler: _API_CreateRequest_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteRequest",
|
||||
Handler: _API_DeleteRequest_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetAssignment",
|
||||
Handler: _API_GetAssignment_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteAssignment",
|
||||
Handler: _API_DeleteAssignment_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "frontend.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("frontend.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 260 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0x41, 0x4b, 0xfb, 0x40,
|
||||
0x10, 0xc5, 0x9b, 0xfc, 0x69, 0xda, 0x0e, 0x34, 0xff, 0xba, 0x78, 0x08, 0x39, 0x88, 0xec, 0xa9,
|
||||
0x20, 0xee, 0x41, 0x0f, 0x7a, 0xf1, 0x50, 0x2a, 0x94, 0xdc, 0x4a, 0xfc, 0x00, 0x52, 0x93, 0x69,
|
||||
0x59, 0x88, 0xbb, 0x71, 0x66, 0x72, 0xf0, 0x0b, 0xf9, 0x39, 0xc5, 0x4d, 0x6b, 0x55, 0xc4, 0xe3,
|
||||
0xfb, 0xed, 0x7b, 0x8f, 0x7d, 0x03, 0xe9, 0x96, 0xbc, 0x13, 0x74, 0xb5, 0x69, 0xc9, 0x8b, 0xd7,
|
||||
0x37, 0x30, 0x5c, 0x91, 0xef, 0x5a, 0x95, 0x42, 0x6c, 0xeb, 0x2c, 0x3a, 0x8f, 0xe6, 0x93, 0x32,
|
||||
0xb6, 0xb5, 0x3a, 0x03, 0x68, 0xc9, 0xb7, 0x48, 0x62, 0x91, 0xb3, 0x38, 0xf0, 0x2f, 0x44, 0xe7,
|
||||
0x30, 0x5e, 0x37, 0x9b, 0x57, 0xa4, 0xa2, 0xfe, 0x99, 0xd5, 0x77, 0x90, 0x2e, 0xbd, 0x73, 0x58,
|
||||
0x89, 0xf5, 0xae, 0x70, 0x5b, 0xaf, 0x2e, 0xe0, 0xa4, 0xfa, 0x24, 0x8f, 0x2c, 0x64, 0xdd, 0x6e,
|
||||
0x1f, 0x98, 0x1d, 0x1f, 0x1e, 0x02, 0xd7, 0xb7, 0x90, 0x94, 0xc8, 0x5d, 0x23, 0x2a, 0x83, 0x11,
|
||||
0x77, 0x55, 0x85, 0xcc, 0xc1, 0x3c, 0x2e, 0x0f, 0x52, 0x9d, 0xc2, 0x10, 0x89, 0x3c, 0xed, 0x7f,
|
||||
0xd6, 0x8b, 0xab, 0xb7, 0x08, 0xfe, 0x2d, 0xd6, 0x85, 0xd2, 0x30, 0x5d, 0x12, 0x6e, 0x04, 0x4b,
|
||||
0x7c, 0xe9, 0x90, 0x45, 0x25, 0x26, 0xac, 0xcc, 0x47, 0xa6, 0x6f, 0xd6, 0x83, 0x0f, 0xcf, 0x3d,
|
||||
0x36, 0xf8, 0xa7, 0xe7, 0x12, 0xa6, 0x2b, 0x94, 0x05, 0xb3, 0xdd, 0xb9, 0x67, 0x74, 0xa2, 0x26,
|
||||
0xe6, 0x30, 0x3a, 0xff, 0x6f, 0xbe, 0x6f, 0xd4, 0x03, 0x35, 0x87, 0x59, 0x5f, 0xf9, 0x7b, 0xe2,
|
||||
0x58, 0xfc, 0x94, 0x84, 0xeb, 0x5f, 0xbf, 0x07, 0x00, 0x00, 0xff, 0xff, 0x2b, 0xde, 0x2c, 0x5b,
|
||||
0x8f, 0x01, 0x00, 0x00,
|
||||
}
|
@ -1,39 +1,14 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// -------------
|
||||
// Follow the guidelines at https://cloud.google.com/endpoints/docs/grpc/transcoding
|
||||
// to keep the gRPC service definitions friendly to REST transcoding. An excerpt:
|
||||
//
|
||||
// "Transcoding involves mapping HTTP/JSON requests and their parameters to gRPC
|
||||
// methods and their parameters and return types (we'll look at exactly how you
|
||||
// do this in the following sections). Because of this, while it's possible to
|
||||
// map an HTTP/JSON request to any arbitrary API method, it's simplest and most
|
||||
// intuitive to do so if the gRPC API itself is structured in a
|
||||
// resource-oriented way, just like a traditional HTTP REST API. In other
|
||||
// words, the API service should be designed so that it uses a small number of
|
||||
// standard methods (corresponding to HTTP verbs like GET, PUT, and so on) that
|
||||
// operate on the service's resources (and collections of resources, which are
|
||||
// themselves a type of resource).
|
||||
// These standard methods are List, Get, Create, Update, and Delete."
|
||||
//
|
||||
// TODO: In a future version, these messages will be moved/merged with those in om_messages.proto
|
||||
syntax = 'proto3';
|
||||
package api;
|
||||
option go_package = "github.com/GoogleCloudPlatform/open-match/internal/pb";
|
||||
import 'api/protobuf-spec/messages.proto';
|
||||
|
||||
service API {
|
||||
rpc CreateRequest(Group) returns (Result) {}
|
||||
rpc DeleteRequest(Group) returns (Result) {}
|
||||
rpc GetAssignment(PlayerId) returns (ConnectionInfo) {}
|
||||
rpc DeleteAssignment(PlayerId) returns (Result) {}
|
||||
service Frontend {
|
||||
rpc CreateRequest(Group) returns (messages.Result) {}
|
||||
rpc DeleteRequest(Group) returns (messages.Result) {}
|
||||
rpc GetAssignment(PlayerId) returns (messages.ConnectionInfo) {}
|
||||
rpc DeleteAssignment(PlayerId) returns (messages.Result) {}
|
||||
}
|
||||
|
||||
// Data structure for a group of players to pass to the matchmaking function.
|
||||
@ -46,14 +21,3 @@ message Group{
|
||||
message PlayerId {
|
||||
string id = 1; // By convention, a UUID
|
||||
}
|
||||
|
||||
// Simple message used to pass the connection string for the DGS to the player.
|
||||
message ConnectionInfo{
|
||||
string connection_string = 1; // Passed by the matchmaker to game clients without modification.
|
||||
}
|
||||
|
||||
// Simple message to return success/failure and error status.
|
||||
message Result{
|
||||
bool success = 1;
|
||||
string error = 2;
|
||||
}
|
||||
|
90
api/protobuf-spec/messages.proto
Normal file
90
api/protobuf-spec/messages.proto
Normal file
@ -0,0 +1,90 @@
|
||||
syntax = 'proto3';
|
||||
package messages;
|
||||
option go_package = "github.com/GoogleCloudPlatform/open-match/internal/pb";
|
||||
|
||||
// Open Match's internal representation and wire protocol format for "MatchObjects".
|
||||
// In order to request a match using the Backend API, your backend code should generate
|
||||
// a new MatchObject with an ID and properties filled in (for more details about valid
|
||||
// values for these fields, see the documentation). Open Match then sends the Match
|
||||
// Object through to your matchmaking function, where you add players to 'rosters' and
|
||||
// store any schemaless data you wish in the 'properties' field. The MatchObject
|
||||
// is then sent, populated, out through the Backend API to your backend code.
|
||||
//
|
||||
// MatchObjects contain a number of fields, but many gRPC calls that take a
|
||||
// MatchObject as input only require a few of them to be filled in. Check the
|
||||
// gRPC function in question for more details.
|
||||
message MatchObject{
|
||||
string id = 1; // By convention, a UUID
|
||||
string properties = 2; // By convention, a JSON-encoded string
|
||||
string error = 3; // Last error encountered.
|
||||
repeated Roster rosters = 4; // Rosters of players.
|
||||
repeated PlayerPool pools = 5; // 'Hard' filters, and the players who match them.
|
||||
}
|
||||
|
||||
// Data structure to hold a list of players in a match.
|
||||
message Roster{
|
||||
string name = 1; // Arbitrary developer-chosen, human-readable string. By convention, set to team name.
|
||||
repeated Player players = 2; // Player profiles on this roster.
|
||||
}
|
||||
|
||||
// A 'hard' filter to apply to the player pool.
|
||||
message Filter{
|
||||
string name = 1; // Arbitrary developer-chosen, human-readable name of this filter. Appears in logs and metrics.
|
||||
string attribute = 2; // Name of the player attribute this filter operates on.
|
||||
int64 maxv = 3; // Maximum value. Defaults to positive infinity (any value above minv).
|
||||
int64 minv = 4; // Minimum value. Defaults to 0.
|
||||
Stats stats = 5; // Statistics for the last time the filter was applied.
|
||||
}
|
||||
|
||||
// Holds statistics
|
||||
message Stats{
|
||||
int64 count = 1; // Number of results.
|
||||
double elapsed = 2; // How long it took to get the results.
|
||||
}
|
||||
|
||||
// PlayerPools are defined by a set of 'hard' filters, and can be filled in
|
||||
// with the players that match those filters.
|
||||
//
|
||||
// PlayerPools contain a number of fields, but many gRPC calls that take a
|
||||
// PlayerPool as input only require a few of them to be filled in. Check the
|
||||
// gRPC function in question for more details.
|
||||
message PlayerPool{
|
||||
string name = 1; // Arbitrary developer-chosen, human-readable string.
|
||||
repeated Filter filters = 2; // Filters are logical AND-ed (a player must match every filter).
|
||||
Roster roster = 3; // Roster of players that match all filters.
|
||||
Stats stats = 4; // Statisticss for the last time this Pool was retrieved from state storage.
|
||||
}
|
||||
|
||||
// Data structure to hold details about a player
|
||||
message Player{
|
||||
message Attribute{
|
||||
string name = 1; // Name should match a Filter.attribute field.
|
||||
int64 value = 2;
|
||||
}
|
||||
string id = 1; // By convention, a UUID
|
||||
string properties = 2; // By convention, a JSON-encoded string
|
||||
string pool = 3; // Optionally used to specify the PlayerPool in which to find a player.
|
||||
repeated Attribute attributes= 4; // Attributes of this player.
|
||||
}
|
||||
|
||||
|
||||
// Simple message to return success/failure and error status.
|
||||
message Result{
|
||||
bool success = 1;
|
||||
string error = 2;
|
||||
}
|
||||
|
||||
// IlInput is an empty message reserved for future use.
|
||||
message IlInput{
|
||||
}
|
||||
|
||||
// Simple message used to pass the connection string for the DGS to the player.
|
||||
// DEPRECATED: Likely to be integrated into another protobuf message in a future version.
|
||||
message ConnectionInfo{
|
||||
string connection_string = 1; // Passed by the matchmaker to game clients without modification.
|
||||
}
|
||||
|
||||
message Assignments{
|
||||
repeated Roster rosters = 1;
|
||||
ConnectionInfo connection_info = 2;
|
||||
}
|
74
api/protobuf-spec/mmlogic.proto
Normal file
74
api/protobuf-spec/mmlogic.proto
Normal file
@ -0,0 +1,74 @@
|
||||
syntax = 'proto3';
|
||||
package api;
|
||||
option go_package = "github.com/GoogleCloudPlatform/open-match/internal/pb";
|
||||
|
||||
// The protobuf messages sent in the gRPC calls are defined 'messages.proto'.
|
||||
import 'api/protobuf-spec/messages.proto';
|
||||
|
||||
// The MMLogic API provides utility functions for common MMF functionality, such
|
||||
// as retreiving profiles and players from state storage, writing results to state storage,
|
||||
// and exposing metrics and statistics.
|
||||
service MmLogic {
|
||||
// Profile and match object functions
|
||||
|
||||
// Send GetProfile a match object with the ID field populated, it will return a
|
||||
// 'filled' one.
|
||||
// Note: filters are assumed to have been checked for validity by the
|
||||
// backendapi when accepting a profile
|
||||
rpc GetProfile(messages.MatchObject) returns (messages.MatchObject) {}
|
||||
|
||||
// CreateProposal is called by MMFs that wish to write their results to
|
||||
// a proposed MatchObject, that can be sent out the Backend API once it has
|
||||
// been approved (by default, by the evaluator process).
|
||||
// - adds all players in all Rosters to the proposed player ignore list
|
||||
// - writes the proposed match to the provided key
|
||||
// - adds that key to the list of proposals to be considered
|
||||
// INPUT:
|
||||
// * TO RETURN A MATCHOBJECT AFTER A SUCCESSFUL MMF RUN
|
||||
// To create a match MatchObject message with these fields populated:
|
||||
// - id, set to the value of the MMF_PROPOSAL_ID env var
|
||||
// - properties
|
||||
// - error. You must explicitly set this to an empty string if your MMF
|
||||
// - roster, with the playerIDs filled in the 'players' repeated field.
|
||||
// - [optional] pools, set to the output from the 'GetPlayerPools' call,
|
||||
// will populate the pools with stats about how many players the filters
|
||||
// matched and how long the filters took to run, which will be sent out
|
||||
// the backend api along with your match results.
|
||||
// was successful.
|
||||
// * TO RETURN AN ERROR
|
||||
// To report a failure or error, send a MatchObject message with these
|
||||
// these fields populated:
|
||||
// - id, set to the value of the MMF_ERROR_ID env var.
|
||||
// - error, set to a string value describing the error your MMF encountered.
|
||||
// - [optional] properties, anything you put here is returned to the
|
||||
// backend along with your error.
|
||||
// - [optional] rosters, anything you put here is returned to the
|
||||
// backend along with your error.
|
||||
// - [optional] pools, set to the output from the 'GetPlayerPools' call,
|
||||
// will populate the pools with stats about how many players the filters
|
||||
// matched and how long the filters took to run, which will be sent out
|
||||
// the backend api along with your match results.
|
||||
// OUTPUT: a Result message with a boolean success value and an error string
|
||||
// if an error was encountered
|
||||
rpc CreateProposal(messages.MatchObject) returns (messages.Result) {}
|
||||
|
||||
// Player listing and filtering functions
|
||||
//
|
||||
// RetrievePlayerPool gets the list of players that match every Filter in the
|
||||
// PlayerPool, .excluding players in any configured ignore lists. It
|
||||
// combines the results, and returns the resulting player pool.
|
||||
rpc GetPlayerPool(messages.PlayerPool) returns (stream messages.PlayerPool) {}
|
||||
|
||||
// Ignore List functions
|
||||
//
|
||||
// IlInput is an empty message reserved for future use.
|
||||
rpc GetAllIgnoredPlayers(messages.IlInput) returns (messages.Roster) {}
|
||||
// ListIgnoredPlayers retrieves players from the ignore list specified in the
|
||||
// config file under 'ignoreLists.proposed.name'.
|
||||
rpc ListIgnoredPlayers(messages.IlInput) returns (messages.Roster) {}
|
||||
|
||||
// NYI
|
||||
// UpdateMetrics sends stats about the MMF run to export to a metrics aggregation tool
|
||||
// like Prometheus or StackDriver.
|
||||
// rpc UpdateMetrics(messages.NYI) returns (messages.Results) {}
|
||||
}
|
3
api/protobuf-spec/protoc.sh
Executable file
3
api/protobuf-spec/protoc.sh
Executable file
@ -0,0 +1,3 @@
|
||||
python3 -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. mmlogic.proto
|
||||
python3 -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. messages.proto
|
||||
cp *pb2* $OM/examples/functions/python3/simple/.
|
26
api/protoc_go.sh
Executable file
26
api/protoc_go.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# Script to compile golang versions of the OM proto files
|
||||
#
|
||||
# Copyright 2018 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cd $GOPATH/src
|
||||
protoc \
|
||||
${GOPATH}/src/github.com/GoogleCloudPlatform/open-match/api/protobuf-spec/backend.proto \
|
||||
${GOPATH}/src/github.com/GoogleCloudPlatform/open-match/api/protobuf-spec/frontend.proto \
|
||||
${GOPATH}/src/github.com/GoogleCloudPlatform/open-match/api/protobuf-spec/mmlogic.proto \
|
||||
${GOPATH}/src/github.com/GoogleCloudPlatform/open-match/api/protobuf-spec/messages.proto \
|
||||
-I ${GOPATH}/src/github.com/GoogleCloudPlatform/open-match/ \
|
||||
--go_out=plugins=grpc:$GOPATH/src
|
||||
cd -
|
10
cloudbuild_base.yaml
Normal file
10
cloudbuild_base.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-devbase:latest',
|
||||
'--cache-from=gcr.io/$PROJECT_ID/openmatch-devbase:latest',
|
||||
'-f', 'Dockerfile.base',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-devbase:latest']
|
@ -1,9 +0,0 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-mmf:dev',
|
||||
'-f', 'Dockerfile.mmf',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-mmf:dev']
|
11
cloudbuild_mmf_go.yaml
Normal file
11
cloudbuild_mmf_go.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [ 'pull', 'gcr.io/$PROJECT_ID/openmatch-devbase:latest' ]
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-mmf:go',
|
||||
'-f', 'Dockerfile.mmf_go',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-mmf:go']
|
9
cloudbuild_mmf_php.yaml
Normal file
9
cloudbuild_mmf_php.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-mmf:php',
|
||||
'-f', 'Dockerfile.mmf_php',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-mmf:php']
|
9
cloudbuild_mmf_py3.yaml
Normal file
9
cloudbuild_mmf_py3.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-mmf:py3',
|
||||
'-f', 'Dockerfile.mmf_py3',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-mmf:py3']
|
9
cloudbuild_mmlogicapi.yaml
Normal file
9
cloudbuild_mmlogicapi.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-mmlogicapi:dev',
|
||||
'-f', 'Dockerfile.mmlogicapi',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-mmlogicapi:dev']
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
package apisrv provides an implementation of the gRPC server defined in ../proto/backend.proto
|
||||
package apisrv provides an implementation of the gRPC server defined in ../../../api/protobuf-spec/backend.proto
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
@ -27,9 +27,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
backend "github.com/GoogleCloudPlatform/open-match/cmd/backendapi/proto"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/metrics"
|
||||
backend "github.com/GoogleCloudPlatform/open-match/internal/pb"
|
||||
redisHelpers "github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/ignorelist"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/redispb"
|
||||
"github.com/gogo/protobuf/jsonpb"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/plugin/ocgrpc"
|
||||
"go.opencensus.io/stats"
|
||||
@ -54,8 +58,8 @@ var (
|
||||
beLog = log.WithFields(beLogFields)
|
||||
)
|
||||
|
||||
// BackendAPI implements backend.ApiServer, the server generated by compiling
|
||||
// the protobuf, by fulfilling the backend.APIClient interface.
|
||||
// BackendAPI implements backend API Server, the server generated by compiling
|
||||
// the protobuf, by fulfilling the API Client interface.
|
||||
type BackendAPI struct {
|
||||
grpc *grpc.Server
|
||||
cfg *viper.Viper
|
||||
@ -74,12 +78,12 @@ func New(cfg *viper.Viper, pool *redis.Pool) *BackendAPI {
|
||||
// Add a hook to the logger to auto-count log lines for metrics output thru OpenCensus
|
||||
log.AddHook(metrics.NewHook(BeLogLines, KeySeverity))
|
||||
|
||||
backend.RegisterAPIServer(s.grpc, (*backendAPI)(&s))
|
||||
backend.RegisterBackendServer(s.grpc, (*backendAPI)(&s))
|
||||
beLog.Info("Successfully registered gRPC server")
|
||||
return &s
|
||||
}
|
||||
|
||||
// Open opens the api grpc service, starting it listening on the configured port.
|
||||
// Open starts the api grpc service listening on the configured port.
|
||||
func (s *BackendAPI) Open() error {
|
||||
ln, err := net.Listen("tcp", ":"+s.cfg.GetString("api.backend.port"))
|
||||
if err != nil {
|
||||
@ -105,7 +109,7 @@ func (s *BackendAPI) Open() error {
|
||||
|
||||
// CreateMatch is this service's implementation of the CreateMatch gRPC method
|
||||
// defined in ../proto/backend.proto
|
||||
func (s *backendAPI) CreateMatch(c context.Context, p *backend.Profile) (*backend.MatchObject, error) {
|
||||
func (s *backendAPI) CreateMatch(c context.Context, profile *backend.MatchObject) (*backend.MatchObject, error) {
|
||||
|
||||
// Get a cancel-able context
|
||||
ctx, cancel := context.WithCancel(c)
|
||||
@ -115,54 +119,103 @@ func (s *backendAPI) CreateMatch(c context.Context, p *backend.Profile) (*backen
|
||||
funcName := "CreateMatch"
|
||||
fnCtx, _ := tag.New(ctx, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
beLog = beLog.WithFields(log.Fields{"func": funcName})
|
||||
beLog.WithFields(log.Fields{
|
||||
"profileID": p.Id,
|
||||
}).Info("gRPC call executing")
|
||||
|
||||
// Write profile
|
||||
_, err := redisHelpers.Create(ctx, s.pool, p.Id, p.Properties)
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("Statestorage failure to create match profile")
|
||||
|
||||
// Failure! Return empty match object and the error
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
return &backend.MatchObject{}, err
|
||||
}
|
||||
|
||||
beLog.WithFields(log.Fields{
|
||||
"profileID": p.Id,
|
||||
}).Info("Profile written to statestorage")
|
||||
|
||||
// Generate a request to fill the profile
|
||||
// Generate a request to fill the profile. Make a unique request ID.
|
||||
moID := strings.Replace(uuid.New().String(), "-", "", -1)
|
||||
profileRequestKey := moID + "." + p.Id
|
||||
requestKey := moID + "." + profile.Id
|
||||
|
||||
_, err = redisHelpers.Update(ctx, s.pool, s.cfg.GetString("queues.profiles.name"), profileRequestKey)
|
||||
/*
|
||||
// Debugging logs
|
||||
beLog.Info("Pools nil? ", (profile.Pools == nil))
|
||||
beLog.Info("Pools empty? ", (len(profile.Pools) == 0))
|
||||
beLog.Info("Rosters nil? ", (profile.Rosters == nil))
|
||||
beLog.Info("Rosters empty? ", (len(profile.Rosters) == 0))
|
||||
beLog.Info("config set for json.pools?", s.cfg.IsSet("jsonkeys.pools"))
|
||||
beLog.Info("contents key?", s.cfg.GetString("jsonkeys.pools"))
|
||||
beLog.Info("contents exist?", gjson.Get(profile.Properties, s.cfg.GetString("jsonkeys.pools")).Exists())
|
||||
*/
|
||||
|
||||
// Case where no protobuf pools was passed; check if there's a JSON version in the properties.
|
||||
// This is for backwards compatibility, it is recommended you populate the
|
||||
// pools before calling CreateMatch/ListMatches
|
||||
if profile.Pools == nil && s.cfg.IsSet("jsonkeys.pools") &&
|
||||
gjson.Get(profile.Properties, s.cfg.GetString("jsonkeys.pools")).Exists() {
|
||||
poolsJSON := fmt.Sprintf("{\"pools\": %v}", gjson.Get(profile.Properties, s.cfg.GetString("jsonkeys.pools")).String())
|
||||
ppLog := beLog.WithFields(log.Fields{"jsonkey": s.cfg.GetString("jsonkeys.pools")})
|
||||
ppLog.Info("poolsJSON: ", poolsJSON)
|
||||
|
||||
ppools := &backend.MatchObject{}
|
||||
err := jsonpb.UnmarshalString(poolsJSON, ppools)
|
||||
if err != nil {
|
||||
ppLog.Error("failed to parse JSON to protobuf pools")
|
||||
} else {
|
||||
profile.Pools = ppools.Pools
|
||||
ppLog.Info("parsed JSON to protobuf pools")
|
||||
}
|
||||
}
|
||||
|
||||
// Case where no protobuf roster was passed; check if there's a JSON version in the properties.
|
||||
// This is for backwards compatibility, it is recommended you populate the
|
||||
// pools before calling CreateMatch/ListMatches
|
||||
if profile.Rosters == nil && s.cfg.IsSet("jsonkeys.rosters") &&
|
||||
gjson.Get(profile.Properties, s.cfg.GetString("jsonkeys.rosters")).Exists() {
|
||||
rostersJSON := fmt.Sprintf("{\"rosters\": %v}", gjson.Get(profile.Properties, s.cfg.GetString("jsonkeys.rosters")).String())
|
||||
rLog := beLog.WithFields(log.Fields{"jsonkey": s.cfg.GetString("jsonkeys.rosters")})
|
||||
|
||||
prosters := &backend.MatchObject{}
|
||||
err := jsonpb.UnmarshalString(rostersJSON, prosters)
|
||||
if err != nil {
|
||||
rLog.Error("failed to parse JSON to protobuf rosters")
|
||||
} else {
|
||||
profile.Rosters = prosters.Rosters
|
||||
rLog.Info("parsed JSON to protobuf rosters")
|
||||
}
|
||||
}
|
||||
|
||||
// Add fields for all subsequent logging
|
||||
beLog = beLog.WithFields(log.Fields{
|
||||
"profileID": profile.Id,
|
||||
"func": funcName,
|
||||
"matchObjectID": moID,
|
||||
"requestKey": requestKey,
|
||||
})
|
||||
beLog.Info("gRPC call executing")
|
||||
beLog.Info("profile is")
|
||||
beLog.Info(profile)
|
||||
|
||||
// Write profile to state storage
|
||||
//_, err := redisHelpers.Create(ctx, s.pool, profile.Id, profile.Properties)
|
||||
err := redispb.MarshalToRedis(ctx, profile, s.pool)
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("Statestorage failure to queue profile")
|
||||
}).Error("State storage failure to create match profile")
|
||||
|
||||
// Failure! Return empty match object and the error
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
return &backend.MatchObject{}, err
|
||||
}
|
||||
beLog.Info("Profile written to state storage")
|
||||
|
||||
beLog.WithFields(log.Fields{
|
||||
"profileID": p.Id,
|
||||
"matchObjectID": moID,
|
||||
"profileRequestKey": profileRequestKey,
|
||||
}).Info("Profile added to processing queue")
|
||||
// Queue the request ID to be sent to an MMF
|
||||
_, err = redisHelpers.Update(ctx, s.pool, s.cfg.GetString("queues.profiles.name"), requestKey)
|
||||
if err != nil {
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("State storage failure to queue profile")
|
||||
|
||||
// get and return matchobject
|
||||
watchChan := redisHelpers.Watcher(ctx, s.pool, profileRequestKey) // Watcher() runs the appropriate Redis commands.
|
||||
mo := &backend.MatchObject{Id: p.Id, Properties: ""}
|
||||
errString := ("Error retrieving matchmaking results from statestorage")
|
||||
// Failure! Return empty match object and the error
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
return &backend.MatchObject{}, err
|
||||
}
|
||||
beLog.Info("Profile added to processing queue")
|
||||
|
||||
// get and return matchobject, it will be written to the requestKey when the MMF has finished.
|
||||
var ok bool
|
||||
newMO := backend.MatchObject{Id: requestKey}
|
||||
watchChan := redispb.Watcher(ctx, s.pool, newMO) // Watcher() runs the appropriate Redis commands.
|
||||
errString := ("Error retrieving matchmaking results from state storage")
|
||||
timeout := time.Duration(s.cfg.GetInt("interval.resultsTimeout")) * time.Second
|
||||
|
||||
select {
|
||||
@ -170,57 +223,40 @@ func (s *backendAPI) CreateMatch(c context.Context, p *backend.Profile) (*backen
|
||||
// TODO:Timeout: deal with the fallout. There are some edge cases here.
|
||||
// When there is a timeout, need to send a stop to the watch channel.
|
||||
stats.Record(fnCtx, BeGrpcRequests.M(1))
|
||||
return mo, errors.New(errString + ": timeout exceeded")
|
||||
return profile, errors.New(errString + ": timeout exceeded")
|
||||
|
||||
case properties, ok := <-watchChan:
|
||||
case newMO, ok = <-watchChan:
|
||||
if !ok {
|
||||
// ok is false if watchChan has been closed by redisHelpers.Watcher()
|
||||
stats.Record(fnCtx, BeGrpcRequests.M(1))
|
||||
return mo, errors.New(errString + ": channel closed - was the context cancelled?")
|
||||
// ok is false if watchChan has been closed by redispb.Watcher()
|
||||
newMO.Error = newMO.Error + "; channel closed - was the context cancelled?"
|
||||
} else {
|
||||
// 'ok' was true, so properties should contain the results from redis.
|
||||
// Do basic error checking on the returned JSON
|
||||
if !gjson.Valid(profile.Properties) {
|
||||
newMO.Error = "retreived properties json was malformed"
|
||||
}
|
||||
}
|
||||
|
||||
beLog.WithFields(log.Fields{
|
||||
"profileRequestKey": profileRequestKey,
|
||||
"matchObjectID": moID,
|
||||
// DEBUG ONLY: This prints the entire result from redis to the logs
|
||||
"matchProperties": properties, // very verbose!
|
||||
}).Debug("Received match object from statestorage")
|
||||
|
||||
// 'ok' was true, so properties should contain the results from redis.
|
||||
// Do some error checking on the returned JSON
|
||||
if !gjson.Valid(properties) {
|
||||
// Just splitting this across lines for readability/wrappability
|
||||
thisError := ": Retreived json was malformed"
|
||||
thisError = thisError + " - did the evaluator write a valid JSON match object?"
|
||||
// TODO test that this is the correct condition for an empty error.
|
||||
if newMO.Error != "" {
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
return mo, errors.New(errString + thisError)
|
||||
return &newMO, errors.New(newMO.Error)
|
||||
}
|
||||
|
||||
mmfError := gjson.Get(properties, "error")
|
||||
if mmfError.Exists() {
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
return mo, errors.New(errString + ": " + mmfError.String())
|
||||
}
|
||||
|
||||
// Passed error checking; safe to send this property blob to the calling client.
|
||||
mo.Properties = properties
|
||||
// Got results; close the channel so the Watcher() function stops querying redis.
|
||||
}
|
||||
|
||||
beLog.WithFields(log.Fields{
|
||||
"profileID": p.Id,
|
||||
"matchObjectID": moID,
|
||||
"profileRequestKey": profileRequestKey,
|
||||
}).Info("Matchmaking results received, returning to backend client")
|
||||
beLog.Info("Matchmaking results received, returning to backend client")
|
||||
|
||||
stats.Record(fnCtx, BeGrpcRequests.M(1))
|
||||
return mo, err
|
||||
return &newMO, err
|
||||
}
|
||||
|
||||
// ListMatches is this service's implementation of the ListMatches gRPC method
|
||||
// defined in ../proto/backend.proto
|
||||
// This is the steaming version of CreateMatch - continually submitting the profile to be filled
|
||||
// until the requesting service ends the connection.
|
||||
func (s *backendAPI) ListMatches(p *backend.Profile, matchStream backend.API_ListMatchesServer) error {
|
||||
// defined in api/protobuf-spec/backend.proto
|
||||
// This is the streaming version of CreateMatch - continually submitting the
|
||||
// profile to be filled until the requesting service ends the connection.
|
||||
func (s *backendAPI) ListMatches(p *backend.MatchObject, matchStream backend.Backend_ListMatchesServer) error {
|
||||
|
||||
// call creatematch in infinite loop as long as the stream is open
|
||||
ctx := matchStream.Context() // https://talks.golang.org/2015/gotham-grpc.slide#30
|
||||
@ -248,7 +284,13 @@ func (s *backendAPI) ListMatches(p *backend.Profile, matchStream backend.API_Lis
|
||||
|
||||
default:
|
||||
// Retreive results from Redis
|
||||
mo, err := s.CreateMatch(ctx, p)
|
||||
requestProfile := proto.Clone(p).(*backend.MatchObject)
|
||||
/*
|
||||
beLog.Debug("new profile requested!")
|
||||
beLog.Debug(requestProfile)
|
||||
beLog.Debug(&requestProfile)
|
||||
*/
|
||||
mo, err := s.CreateMatch(ctx, requestProfile)
|
||||
|
||||
beLog = beLog.WithFields(log.Fields{"func": funcName})
|
||||
|
||||
@ -257,7 +299,7 @@ func (s *backendAPI) ListMatches(p *backend.Profile, matchStream backend.API_Lis
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
return err
|
||||
}
|
||||
beLog.WithFields(log.Fields{"matchProperties": fmt.Sprintf("%v", &mo)}).Debug("Streaming back match object")
|
||||
beLog.WithFields(log.Fields{"matchProperties": fmt.Sprintf("%v", mo)}).Debug("Streaming back match object")
|
||||
matchStream.Send(mo)
|
||||
|
||||
// TODO: This should be tunable, but there should be SOME sleep here, to give a requestor a window
|
||||
@ -286,7 +328,7 @@ func (s *backendAPI) DeleteMatch(ctx context.Context, mo *backend.MatchObject) (
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("Statestorage error")
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
return &backend.Result{Success: false, Error: err.Error()}, err
|
||||
@ -304,8 +346,10 @@ func (s *backendAPI) DeleteMatch(ctx context.Context, mo *backend.MatchObject) (
|
||||
// defined in ../proto/backend.proto
|
||||
func (s *backendAPI) CreateAssignments(ctx context.Context, a *backend.Assignments) (*backend.Result, error) {
|
||||
|
||||
// TODO: make playerIDs a repeated protobuf message field and iterate over it
|
||||
assignments := strings.Split(a.Roster.PlayerIds, " ")
|
||||
assignments := make([]string, 0)
|
||||
for _, roster := range a.Rosters {
|
||||
assignments = append(assignments, getPlayerIdsFromRoster(roster)...)
|
||||
}
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "CreateAssignments"
|
||||
@ -320,16 +364,22 @@ func (s *backendAPI) CreateAssignments(ctx context.Context, a *backend.Assignmen
|
||||
redisConn := s.pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
// Create player assignments in a transaction
|
||||
// Create player assignments in a transaction.
|
||||
redisConn.Send("MULTI")
|
||||
for _, playerID := range assignments {
|
||||
beLog.WithFields(log.Fields{
|
||||
"query": "HSET",
|
||||
"playerID": playerID,
|
||||
s.cfg.GetString("jsonkeys.connstring"): a.ConnectionInfo.ConnectionString,
|
||||
}).Debug("Statestorage operation")
|
||||
}).Debug("state storage operation")
|
||||
redisConn.Send("HSET", playerID, s.cfg.GetString("jsonkeys.connstring"), a.ConnectionInfo.ConnectionString)
|
||||
}
|
||||
// Remove these players from the proposed list.
|
||||
ignorelist.SendRemove(redisConn, "proposed", assignments)
|
||||
// Add these players from the deindexed list.
|
||||
ignorelist.SendAdd(redisConn, "deindexed", assignments)
|
||||
|
||||
// Send the multi-command transaction to Redis.
|
||||
_, err := redisConn.Do("EXEC")
|
||||
|
||||
// Issue encountered
|
||||
@ -337,7 +387,7 @@ func (s *backendAPI) CreateAssignments(ctx context.Context, a *backend.Assignmen
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("Statestorage error")
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
stats.Record(fnCtx, BeAssignmentFailures.M(int64(len(assignments))))
|
||||
@ -356,9 +406,10 @@ func (s *backendAPI) CreateAssignments(ctx context.Context, a *backend.Assignmen
|
||||
|
||||
// DeleteAssignments is this service's implementation of the DeleteAssignments gRPC method
|
||||
// defined in ../proto/backend.proto
|
||||
func (s *backendAPI) DeleteAssignments(ctx context.Context, a *backend.Roster) (*backend.Result, error) {
|
||||
func (s *backendAPI) DeleteAssignments(ctx context.Context, r *backend.Roster) (*backend.Result, error) {
|
||||
// TODO: make playerIDs a repeated protobuf message field and iterate over it
|
||||
assignments := strings.Split(a.PlayerIds, " ")
|
||||
//assignments := strings.Split(a.PlayerIds, " ")
|
||||
assignments := getPlayerIdsFromRoster(r)
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "DeleteAssignments"
|
||||
@ -377,7 +428,7 @@ func (s *backendAPI) DeleteAssignments(ctx context.Context, a *backend.Roster) (
|
||||
redisConn.Send("MULTI")
|
||||
// TODO: make playerIDs a repeated protobuf message field and iterate over it
|
||||
for _, playerID := range assignments {
|
||||
beLog.WithFields(log.Fields{"query": "DEL", "key": playerID}).Debug("Statestorage operation")
|
||||
beLog.WithFields(log.Fields{"query": "DEL", "key": playerID}).Debug("state storage operation")
|
||||
redisConn.Send("DEL", playerID)
|
||||
}
|
||||
_, err := redisConn.Do("EXEC")
|
||||
@ -387,7 +438,7 @@ func (s *backendAPI) DeleteAssignments(ctx context.Context, a *backend.Roster) (
|
||||
beLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("Statestorage error")
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, BeGrpcErrors.M(1))
|
||||
stats.Record(fnCtx, BeAssignmentDeletionFailures.M(int64(len(assignments))))
|
||||
@ -399,3 +450,12 @@ func (s *backendAPI) DeleteAssignments(ctx context.Context, a *backend.Roster) (
|
||||
stats.Record(fnCtx, BeAssignmentDeletions.M(int64(len(assignments))))
|
||||
return &backend.Result{Success: true, Error: ""}, err
|
||||
}
|
||||
|
||||
func getPlayerIdsFromRoster(r *backend.Roster) []string {
|
||||
playerIDs := make([]string, 0)
|
||||
for _, p := range r.Players {
|
||||
playerIDs = append(playerIDs, p.Id)
|
||||
}
|
||||
return playerIDs
|
||||
|
||||
}
|
||||
|
@ -1,17 +1,3 @@
|
||||
/*
|
||||
Copyright 2018 Google LLC
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
/*
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: backend.proto
|
||||
|
||||
@ -24,8 +10,14 @@ It is generated from these files:
|
||||
It has these top-level messages:
|
||||
Profile
|
||||
MatchObject
|
||||
Result
|
||||
Roster
|
||||
Filter
|
||||
Stats
|
||||
PlayerPool
|
||||
Player
|
||||
Result
|
||||
IlInput
|
||||
Timestamp
|
||||
ConnectionInfo
|
||||
Assignments
|
||||
*/
|
||||
@ -51,10 +43,19 @@ var _ = math.Inf
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// Data structure for a profile to pass to the matchmaking function.
|
||||
type Profile struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
Properties string `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"`
|
||||
Name string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"`
|
||||
// When you send a Profile to the backendAPI, it looks to see if you populated
|
||||
// this field with protobuf-encoded PlayerPool objects containing valid the filters
|
||||
// objects. If you did, they are used by OM. If you didn't, the backendAPI
|
||||
// next looks in your properties blob at the key specified in the 'jsonkeys.pools'
|
||||
// config value from config/matchmaker_config.json - If it finds valid player
|
||||
// pool definitions at that key, it will try to unmarshal them into this field.
|
||||
// If you didn't specify valid player pools in either place, OM assumes you
|
||||
// know what you're doing and just leaves this unpopulatd.
|
||||
Pools []*PlayerPool `protobuf:"bytes,4,rep,name=pools" json:"pools,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Profile) Reset() { *m = Profile{} }
|
||||
@ -76,10 +77,26 @@ func (m *Profile) GetProperties() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Data structure for all the properties of a match.
|
||||
func (m *Profile) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Profile) GetPools() []*PlayerPool {
|
||||
if m != nil {
|
||||
return m.Pools
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A MMF takes the Profile object above, and generates a MatchObject.
|
||||
type MatchObject struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
Properties string `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
Properties string `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"`
|
||||
Rosters []*Roster `protobuf:"bytes,3,rep,name=rosters" json:"rosters,omitempty"`
|
||||
Pools []*PlayerPool `protobuf:"bytes,4,rep,name=pools" json:"pools,omitempty"`
|
||||
}
|
||||
|
||||
func (m *MatchObject) Reset() { *m = MatchObject{} }
|
||||
@ -101,6 +118,223 @@ func (m *MatchObject) GetProperties() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *MatchObject) GetRosters() []*Roster {
|
||||
if m != nil {
|
||||
return m.Rosters
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MatchObject) GetPools() []*PlayerPool {
|
||||
if m != nil {
|
||||
return m.Pools
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Data structure to hold a list of players in a match.
|
||||
type Roster struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
Players []*Player `protobuf:"bytes,2,rep,name=players" json:"players,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Roster) Reset() { *m = Roster{} }
|
||||
func (m *Roster) String() string { return proto.CompactTextString(m) }
|
||||
func (*Roster) ProtoMessage() {}
|
||||
func (*Roster) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
||||
|
||||
func (m *Roster) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Roster) GetPlayers() []*Player {
|
||||
if m != nil {
|
||||
return m.Players
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A filter to apply to the player pool.
|
||||
type Filter struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
Attribute string `protobuf:"bytes,2,opt,name=attribute" json:"attribute,omitempty"`
|
||||
Maxv int64 `protobuf:"varint,3,opt,name=maxv" json:"maxv,omitempty"`
|
||||
Minv int64 `protobuf:"varint,4,opt,name=minv" json:"minv,omitempty"`
|
||||
Stats *Stats `protobuf:"bytes,5,opt,name=stats" json:"stats,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Filter) Reset() { *m = Filter{} }
|
||||
func (m *Filter) String() string { return proto.CompactTextString(m) }
|
||||
func (*Filter) ProtoMessage() {}
|
||||
func (*Filter) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
|
||||
|
||||
func (m *Filter) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Filter) GetAttribute() string {
|
||||
if m != nil {
|
||||
return m.Attribute
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Filter) GetMaxv() int64 {
|
||||
if m != nil {
|
||||
return m.Maxv
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Filter) GetMinv() int64 {
|
||||
if m != nil {
|
||||
return m.Minv
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Filter) GetStats() *Stats {
|
||||
if m != nil {
|
||||
return m.Stats
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
Count int64 `protobuf:"varint,1,opt,name=count" json:"count,omitempty"`
|
||||
Elapsed float64 `protobuf:"fixed64,2,opt,name=elapsed" json:"elapsed,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Stats) Reset() { *m = Stats{} }
|
||||
func (m *Stats) String() string { return proto.CompactTextString(m) }
|
||||
func (*Stats) ProtoMessage() {}
|
||||
func (*Stats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
|
||||
|
||||
func (m *Stats) GetCount() int64 {
|
||||
if m != nil {
|
||||
return m.Count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Stats) GetElapsed() float64 {
|
||||
if m != nil {
|
||||
return m.Elapsed
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type PlayerPool struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
Filters []*Filter `protobuf:"bytes,2,rep,name=filters" json:"filters,omitempty"`
|
||||
Roster *Roster `protobuf:"bytes,3,opt,name=roster" json:"roster,omitempty"`
|
||||
Stats *Stats `protobuf:"bytes,4,opt,name=stats" json:"stats,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PlayerPool) Reset() { *m = PlayerPool{} }
|
||||
func (m *PlayerPool) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlayerPool) ProtoMessage() {}
|
||||
func (*PlayerPool) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
|
||||
|
||||
func (m *PlayerPool) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *PlayerPool) GetFilters() []*Filter {
|
||||
if m != nil {
|
||||
return m.Filters
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PlayerPool) GetRoster() *Roster {
|
||||
if m != nil {
|
||||
return m.Roster
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PlayerPool) GetStats() *Stats {
|
||||
if m != nil {
|
||||
return m.Stats
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Data structure for a profile to pass to the matchmaking function.
|
||||
type Player struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
Properties string `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"`
|
||||
Pool string `protobuf:"bytes,3,opt,name=pool" json:"pool,omitempty"`
|
||||
Attributes []*Player_Attribute `protobuf:"bytes,4,rep,name=attributes" json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Player) Reset() { *m = Player{} }
|
||||
func (m *Player) String() string { return proto.CompactTextString(m) }
|
||||
func (*Player) ProtoMessage() {}
|
||||
func (*Player) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
|
||||
|
||||
func (m *Player) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Player) GetProperties() string {
|
||||
if m != nil {
|
||||
return m.Properties
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Player) GetPool() string {
|
||||
if m != nil {
|
||||
return m.Pool
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Player) GetAttributes() []*Player_Attribute {
|
||||
if m != nil {
|
||||
return m.Attributes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Player_Attribute struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
Value int64 `protobuf:"varint,2,opt,name=value" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Player_Attribute) Reset() { *m = Player_Attribute{} }
|
||||
func (m *Player_Attribute) String() string { return proto.CompactTextString(m) }
|
||||
func (*Player_Attribute) ProtoMessage() {}
|
||||
func (*Player_Attribute) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6, 0} }
|
||||
|
||||
func (m *Player_Attribute) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Player_Attribute) GetValue() int64 {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Simple message to return success/failure and error status.
|
||||
type Result struct {
|
||||
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
|
||||
@ -110,7 +344,7 @@ type Result struct {
|
||||
func (m *Result) Reset() { *m = Result{} }
|
||||
func (m *Result) String() string { return proto.CompactTextString(m) }
|
||||
func (*Result) ProtoMessage() {}
|
||||
func (*Result) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
||||
func (*Result) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
|
||||
|
||||
func (m *Result) GetSuccess() bool {
|
||||
if m != nil {
|
||||
@ -126,21 +360,30 @@ func (m *Result) GetError() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Data structure to hold a list of players in a match.
|
||||
type Roster struct {
|
||||
PlayerIds string `protobuf:"bytes,1,opt,name=player_ids,json=playerIds" json:"player_ids,omitempty"`
|
||||
// IlInput is an empty message reserved for future use.
|
||||
type IlInput struct {
|
||||
}
|
||||
|
||||
func (m *Roster) Reset() { *m = Roster{} }
|
||||
func (m *Roster) String() string { return proto.CompactTextString(m) }
|
||||
func (*Roster) ProtoMessage() {}
|
||||
func (*Roster) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
|
||||
func (m *IlInput) Reset() { *m = IlInput{} }
|
||||
func (m *IlInput) String() string { return proto.CompactTextString(m) }
|
||||
func (*IlInput) ProtoMessage() {}
|
||||
func (*IlInput) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
|
||||
|
||||
func (m *Roster) GetPlayerIds() string {
|
||||
// Epoch timestamp in seconds.
|
||||
type Timestamp struct {
|
||||
Ts int64 `protobuf:"varint,1,opt,name=ts" json:"ts,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Timestamp) Reset() { *m = Timestamp{} }
|
||||
func (m *Timestamp) String() string { return proto.CompactTextString(m) }
|
||||
func (*Timestamp) ProtoMessage() {}
|
||||
func (*Timestamp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
|
||||
|
||||
func (m *Timestamp) GetTs() int64 {
|
||||
if m != nil {
|
||||
return m.PlayerIds
|
||||
return m.Ts
|
||||
}
|
||||
return ""
|
||||
return 0
|
||||
}
|
||||
|
||||
// Simple message used to pass the connection string for the DGS to the player.
|
||||
@ -151,7 +394,7 @@ type ConnectionInfo struct {
|
||||
func (m *ConnectionInfo) Reset() { *m = ConnectionInfo{} }
|
||||
func (m *ConnectionInfo) String() string { return proto.CompactTextString(m) }
|
||||
func (*ConnectionInfo) ProtoMessage() {}
|
||||
func (*ConnectionInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
|
||||
func (*ConnectionInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} }
|
||||
|
||||
func (m *ConnectionInfo) GetConnectionString() string {
|
||||
if m != nil {
|
||||
@ -161,18 +404,18 @@ func (m *ConnectionInfo) GetConnectionString() string {
|
||||
}
|
||||
|
||||
type Assignments struct {
|
||||
Roster *Roster `protobuf:"bytes,1,opt,name=roster" json:"roster,omitempty"`
|
||||
Rosters []*Roster `protobuf:"bytes,1,rep,name=rosters" json:"rosters,omitempty"`
|
||||
ConnectionInfo *ConnectionInfo `protobuf:"bytes,2,opt,name=connection_info,json=connectionInfo" json:"connection_info,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Assignments) Reset() { *m = Assignments{} }
|
||||
func (m *Assignments) String() string { return proto.CompactTextString(m) }
|
||||
func (*Assignments) ProtoMessage() {}
|
||||
func (*Assignments) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
|
||||
func (*Assignments) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} }
|
||||
|
||||
func (m *Assignments) GetRoster() *Roster {
|
||||
func (m *Assignments) GetRosters() []*Roster {
|
||||
if m != nil {
|
||||
return m.Roster
|
||||
return m.Rosters
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -187,8 +430,15 @@ func (m *Assignments) GetConnectionInfo() *ConnectionInfo {
|
||||
func init() {
|
||||
proto.RegisterType((*Profile)(nil), "Profile")
|
||||
proto.RegisterType((*MatchObject)(nil), "MatchObject")
|
||||
proto.RegisterType((*Result)(nil), "Result")
|
||||
proto.RegisterType((*Roster)(nil), "Roster")
|
||||
proto.RegisterType((*Filter)(nil), "Filter")
|
||||
proto.RegisterType((*Stats)(nil), "Stats")
|
||||
proto.RegisterType((*PlayerPool)(nil), "PlayerPool")
|
||||
proto.RegisterType((*Player)(nil), "Player")
|
||||
proto.RegisterType((*Player_Attribute)(nil), "Player.Attribute")
|
||||
proto.RegisterType((*Result)(nil), "Result")
|
||||
proto.RegisterType((*IlInput)(nil), "IlInput")
|
||||
proto.RegisterType((*Timestamp)(nil), "Timestamp")
|
||||
proto.RegisterType((*ConnectionInfo)(nil), "ConnectionInfo")
|
||||
proto.RegisterType((*Assignments)(nil), "Assignments")
|
||||
}
|
||||
@ -214,14 +464,15 @@ type APIClient interface {
|
||||
// Delete a matchobject from state storage manually. (Matchobjects in state
|
||||
// storage will also automatically expire after a while)
|
||||
DeleteMatch(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*Result, error)
|
||||
// Call that manage communication of DGS connection info to players.
|
||||
// Call for communication of connection info to players.
|
||||
//
|
||||
// Write the DGS connection info for the list of players in the
|
||||
// Assignments.roster to state storage, so that info can be read by the game
|
||||
// client(s).
|
||||
// Write the connection info for the list of players in the
|
||||
// Assignments.Rosters to state storage. The FrontendAPI is responsible for
|
||||
// sending anything written here to the game clients.
|
||||
// TODO: change this to be agnostic; return a 'result' instead of a connection
|
||||
// string so it can be integrated with session service etc
|
||||
CreateAssignments(ctx context.Context, in *Assignments, opts ...grpc.CallOption) (*Result, error)
|
||||
// Remove DGS connection info for the list of players in the Roster from
|
||||
// state storage.
|
||||
// Remove DGS connection info from state storage for all players in the Roster.
|
||||
DeleteAssignments(ctx context.Context, in *Roster, opts ...grpc.CallOption) (*Result, error)
|
||||
}
|
||||
|
||||
@ -314,14 +565,15 @@ type APIServer interface {
|
||||
// Delete a matchobject from state storage manually. (Matchobjects in state
|
||||
// storage will also automatically expire after a while)
|
||||
DeleteMatch(context.Context, *MatchObject) (*Result, error)
|
||||
// Call that manage communication of DGS connection info to players.
|
||||
// Call for communication of connection info to players.
|
||||
//
|
||||
// Write the DGS connection info for the list of players in the
|
||||
// Assignments.roster to state storage, so that info can be read by the game
|
||||
// client(s).
|
||||
// Write the connection info for the list of players in the
|
||||
// Assignments.Rosters to state storage. The FrontendAPI is responsible for
|
||||
// sending anything written here to the game clients.
|
||||
// TODO: change this to be agnostic; return a 'result' instead of a connection
|
||||
// string so it can be integrated with session service etc
|
||||
CreateAssignments(context.Context, *Assignments) (*Result, error)
|
||||
// Remove DGS connection info for the list of players in the Roster from
|
||||
// state storage.
|
||||
// Remove DGS connection info from state storage for all players in the Roster.
|
||||
DeleteAssignments(context.Context, *Roster) (*Result, error)
|
||||
}
|
||||
|
||||
@ -456,27 +708,42 @@ var _API_serviceDesc = grpc.ServiceDesc{
|
||||
func init() { proto.RegisterFile("backend.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 344 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xcf, 0x4e, 0xc2, 0x40,
|
||||
0x10, 0xc6, 0x29, 0xc6, 0x16, 0x66, 0x11, 0x64, 0xe3, 0x81, 0x90, 0xf8, 0x27, 0x3d, 0x88, 0x46,
|
||||
0xb3, 0x31, 0x78, 0xc1, 0x83, 0x07, 0x82, 0x17, 0x12, 0x8d, 0xa4, 0x3e, 0x00, 0x29, 0xdb, 0x01,
|
||||
0x56, 0xeb, 0x6e, 0xb3, 0xbb, 0x1c, 0x7c, 0x53, 0x1f, 0xc7, 0xb8, 0x2d, 0xba, 0x1c, 0x3c, 0x78,
|
||||
0x9c, 0x5f, 0xbf, 0x6f, 0xe6, 0xeb, 0xcc, 0xc2, 0xc1, 0x22, 0xe5, 0x6f, 0x28, 0x33, 0x56, 0x68,
|
||||
0x65, 0x55, 0x7c, 0x07, 0xd1, 0x4c, 0xab, 0xa5, 0xc8, 0x91, 0xb6, 0xa1, 0x2e, 0xb2, 0x5e, 0x70,
|
||||
0x16, 0x5c, 0x34, 0x93, 0xba, 0xc8, 0xe8, 0x09, 0x40, 0xa1, 0x55, 0x81, 0xda, 0x0a, 0x34, 0xbd,
|
||||
0xba, 0xe3, 0x1e, 0x89, 0xef, 0x81, 0x3c, 0xa5, 0x96, 0xaf, 0x9f, 0x17, 0xaf, 0xc8, 0xed, 0xbf,
|
||||
0xed, 0x23, 0x08, 0x13, 0x34, 0x9b, 0xdc, 0xd2, 0x1e, 0x44, 0x66, 0xc3, 0x39, 0x1a, 0xe3, 0xec,
|
||||
0x8d, 0x64, 0x5b, 0xd2, 0x23, 0xd8, 0x47, 0xad, 0x95, 0xae, 0xec, 0x65, 0x11, 0x0f, 0x20, 0x4c,
|
||||
0x94, 0xb1, 0xa8, 0xe9, 0x31, 0x40, 0x91, 0xa7, 0x1f, 0xa8, 0xe7, 0x22, 0x33, 0xd5, 0xec, 0x66,
|
||||
0x49, 0xa6, 0xd9, 0x77, 0xc2, 0xf6, 0x44, 0x49, 0x89, 0xdc, 0x0a, 0x25, 0xa7, 0x72, 0xa9, 0xe8,
|
||||
0x15, 0x74, 0xf9, 0x0f, 0x99, 0x1b, 0xab, 0x85, 0x5c, 0x55, 0xbe, 0xc3, 0xdf, 0x0f, 0x2f, 0x8e,
|
||||
0xc7, 0x6b, 0x20, 0x63, 0x63, 0xc4, 0x4a, 0xbe, 0xa3, 0xb4, 0x86, 0x9e, 0x42, 0xa8, 0xdd, 0x58,
|
||||
0x67, 0x20, 0xc3, 0x88, 0x95, 0x29, 0x92, 0x0a, 0xd3, 0x11, 0x74, 0xbc, 0xe6, 0x42, 0x2e, 0x95,
|
||||
0xcb, 0x4d, 0x86, 0x1d, 0xb6, 0x1b, 0x23, 0x69, 0xf3, 0x9d, 0x7a, 0xf8, 0x19, 0xc0, 0xde, 0x78,
|
||||
0x36, 0xa5, 0x03, 0x20, 0x13, 0x8d, 0xa9, 0x45, 0xb7, 0x58, 0xda, 0x60, 0xd5, 0x6d, 0xfa, 0x2d,
|
||||
0xe6, 0xad, 0x3a, 0xae, 0xd1, 0x4b, 0x20, 0x8f, 0xc2, 0x58, 0x07, 0xd1, 0xfc, 0x2d, 0xbc, 0x09,
|
||||
0xe8, 0x39, 0x90, 0x07, 0xcc, 0x71, 0xdb, 0x73, 0x47, 0xd0, 0x8f, 0x58, 0x79, 0x83, 0xb8, 0x46,
|
||||
0xaf, 0xa1, 0x5b, 0xce, 0xf6, 0xff, 0xb9, 0xc5, 0xbc, 0xca, 0x57, 0x0f, 0xa0, 0x5b, 0x76, 0xf5,
|
||||
0xd5, 0xdb, 0x8d, 0x78, 0xc2, 0x45, 0xe8, 0xde, 0xd9, 0xed, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xf2, 0x23, 0x14, 0x36, 0x78, 0x02, 0x00, 0x00,
|
||||
// 591 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0x51, 0x6f, 0xd3, 0x30,
|
||||
0x10, 0x9e, 0x9b, 0x26, 0x59, 0x2f, 0x63, 0xa3, 0xd6, 0x1e, 0xa2, 0x31, 0x41, 0xe7, 0x07, 0x56,
|
||||
0x04, 0x8a, 0xa0, 0x08, 0xb1, 0x17, 0x84, 0xaa, 0x21, 0xa4, 0x4a, 0x20, 0x2a, 0x8f, 0x77, 0x94,
|
||||
0xa6, 0xee, 0xf0, 0x48, 0xed, 0xc8, 0x76, 0x2a, 0x78, 0x43, 0xf0, 0x9f, 0xf8, 0x2d, 0xfc, 0x1c,
|
||||
0x14, 0x3b, 0x69, 0x53, 0x41, 0x25, 0xe0, 0xcd, 0xdf, 0xe7, 0xbb, 0xf3, 0x77, 0xdf, 0xe5, 0x02,
|
||||
0xb7, 0x66, 0x69, 0xf6, 0x89, 0x89, 0x79, 0x52, 0x28, 0x69, 0x24, 0x29, 0x20, 0x9c, 0x2a, 0xb9,
|
||||
0xe0, 0x39, 0xc3, 0x87, 0xd0, 0xe1, 0xf3, 0x18, 0x0d, 0xd0, 0xb0, 0x47, 0x3b, 0x7c, 0x8e, 0xef,
|
||||
0x02, 0x14, 0x4a, 0x16, 0x4c, 0x19, 0xce, 0x74, 0xdc, 0xb1, 0x7c, 0x8b, 0xc1, 0x18, 0xba, 0x22,
|
||||
0x5d, 0xb2, 0xd8, 0xb3, 0x37, 0xf6, 0x8c, 0xcf, 0xc0, 0x2f, 0xa4, 0xcc, 0x75, 0xdc, 0x1d, 0x78,
|
||||
0xc3, 0x68, 0x14, 0x25, 0xd3, 0x3c, 0xfd, 0xc2, 0xd4, 0x54, 0xca, 0x9c, 0xba, 0x1b, 0xf2, 0x1d,
|
||||
0x41, 0xf4, 0x36, 0x35, 0xd9, 0xc7, 0x77, 0xb3, 0x1b, 0x96, 0x99, 0x7f, 0x7e, 0xf6, 0x0c, 0x42,
|
||||
0x25, 0xb5, 0x61, 0x4a, 0xc7, 0x9e, 0x7d, 0x24, 0x4c, 0xa8, 0xc5, 0xb4, 0xe1, 0xff, 0x46, 0xc5,
|
||||
0x4b, 0x08, 0x5c, 0xd6, 0xba, 0x0d, 0xb4, 0xd5, 0x46, 0x58, 0xd8, 0x94, 0x4a, 0x80, 0x7b, 0xc3,
|
||||
0x95, 0xa0, 0x0d, 0x4f, 0xbe, 0x22, 0x08, 0x5e, 0xf3, 0x7c, 0x57, 0x85, 0x53, 0xe8, 0xa5, 0xc6,
|
||||
0x28, 0x3e, 0x2b, 0x0d, 0xab, 0x9b, 0xd8, 0x10, 0x55, 0xc6, 0x32, 0xfd, 0xbc, 0xb2, 0xd6, 0x79,
|
||||
0xd4, 0x9e, 0x2d, 0xc7, 0xc5, 0x2a, 0xee, 0xd6, 0x1c, 0x17, 0x2b, 0x7c, 0x0a, 0xbe, 0x36, 0xa9,
|
||||
0xd1, 0xb1, 0x3f, 0x40, 0xc3, 0x68, 0x14, 0x24, 0x57, 0x15, 0xa2, 0x8e, 0x24, 0xcf, 0xc1, 0xb7,
|
||||
0x18, 0x1f, 0x83, 0x9f, 0xc9, 0x52, 0x18, 0xab, 0xc0, 0xa3, 0x0e, 0xe0, 0x18, 0x42, 0x96, 0xa7,
|
||||
0x85, 0x66, 0x73, 0x2b, 0x00, 0xd1, 0x06, 0x92, 0x6f, 0x08, 0x60, 0x63, 0xc9, 0x2e, 0x07, 0x16,
|
||||
0xb6, 0xbb, 0x8d, 0x03, 0xae, 0x5b, 0xda, 0xf0, 0xf8, 0x1e, 0x04, 0xce, 0x70, 0xdb, 0x46, 0x6b,
|
||||
0x0e, 0x35, 0xbd, 0x51, 0xdf, 0xfd, 0x93, 0xfa, 0x1f, 0x08, 0x02, 0x27, 0xe2, 0x7f, 0xbe, 0xbc,
|
||||
0x6a, 0x8a, 0xcd, 0x97, 0x57, 0x9d, 0xf1, 0x13, 0x80, 0xb5, 0xbf, 0xcd, 0xe0, 0xfb, 0xf5, 0xd4,
|
||||
0x92, 0x71, 0x73, 0x43, 0x5b, 0x41, 0x27, 0xcf, 0xa0, 0x37, 0x6e, 0x8f, 0xe4, 0x37, 0x13, 0x8e,
|
||||
0xc1, 0x5f, 0xa5, 0x79, 0xe9, 0x06, 0xe8, 0x51, 0x07, 0xc8, 0x05, 0x04, 0x94, 0xe9, 0x32, 0xb7,
|
||||
0x0e, 0xeb, 0x32, 0xcb, 0x98, 0xd6, 0x36, 0x6d, 0x9f, 0x36, 0xb0, 0xca, 0x64, 0x4a, 0x49, 0x55,
|
||||
0x8b, 0x77, 0x80, 0xf4, 0x20, 0x9c, 0xe4, 0x13, 0x51, 0x94, 0x86, 0xdc, 0x81, 0xde, 0x7b, 0xbe,
|
||||
0x64, 0xda, 0xa4, 0xcb, 0xa2, 0xea, 0xdf, 0xe8, 0x7a, 0x78, 0x1d, 0xa3, 0xc9, 0x0b, 0x38, 0xbc,
|
||||
0x94, 0x42, 0xb0, 0xcc, 0x70, 0x29, 0x26, 0x62, 0x21, 0xf1, 0x43, 0xe8, 0x67, 0x6b, 0xe6, 0x83,
|
||||
0x36, 0x8a, 0x8b, 0xeb, 0x5a, 0xea, 0xed, 0xcd, 0xc5, 0x95, 0xe5, 0xc9, 0x0d, 0x44, 0x63, 0xad,
|
||||
0xf9, 0xb5, 0x58, 0x32, 0x61, 0xb6, 0x16, 0x06, 0xed, 0x58, 0x98, 0x0b, 0x38, 0x6a, 0x95, 0xe7,
|
||||
0x62, 0x21, 0xad, 0xf0, 0x68, 0x74, 0x94, 0x6c, 0x0b, 0xa1, 0x87, 0xd9, 0x16, 0x1e, 0xfd, 0x44,
|
||||
0xe0, 0x8d, 0xa7, 0x13, 0x7c, 0x0e, 0xd1, 0xa5, 0x62, 0xa9, 0x61, 0x76, 0xb5, 0xf1, 0x7e, 0x52,
|
||||
0xff, 0x55, 0x4e, 0x0e, 0x92, 0xd6, 0xb2, 0x93, 0x3d, 0xfc, 0x00, 0xa2, 0x37, 0x5c, 0x1b, 0x4b,
|
||||
0x32, 0xbd, 0x3b, 0xf0, 0x31, 0xc2, 0xf7, 0x21, 0x7a, 0xc5, 0x72, 0xd6, 0xd4, 0xdc, 0x0a, 0x38,
|
||||
0x09, 0x13, 0x37, 0x04, 0xb2, 0x87, 0x1f, 0x41, 0xdf, 0xbd, 0xdd, 0xee, 0xfa, 0x20, 0x69, 0xa1,
|
||||
0x76, 0xf4, 0x39, 0xf4, 0x5d, 0xd5, 0x76, 0x74, 0x63, 0x49, 0x2b, 0x70, 0x16, 0xd8, 0x3f, 0xe4,
|
||||
0xd3, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x04, 0x1a, 0xf8, 0x0a, 0x32, 0x05, 0x00, 0x00,
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
package apisrv provides an implementation of the gRPC server defined in ../proto/frontend.proto.
|
||||
package apisrv provides an implementation of the gRPC server defined in ../../../api/protobuf-spec/frontend.proto.
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
@ -75,7 +75,7 @@ func New(cfg *viper.Viper, pool *redis.Pool) *FrontendAPI {
|
||||
return &s
|
||||
}
|
||||
|
||||
// Open opens the api grpc service, starting it listening on the configured port.
|
||||
// Open starts the api grpc service listening on the configured port.
|
||||
func (s *FrontendAPI) Open() error {
|
||||
ln, err := net.Listen("tcp", ":"+s.cfg.GetString("api.frontend.port"))
|
||||
if err != nil {
|
||||
|
@ -114,8 +114,6 @@ func main() {
|
||||
|
||||
start := time.Now()
|
||||
checkProposals := true
|
||||
defaultMmfImages := []string{cfg.GetString("defaultImages.mmf.name") + ":" + cfg.GetString("defaultImages.mmf.tag")}
|
||||
defaultEvalImage := cfg.GetString("defaultImages.evaluator.name") + ":" + cfg.GetString("defaultImages.evaluator.tag")
|
||||
|
||||
// main loop; kick off matchmaker functions for profiles in the profile
|
||||
// queue and an evaluator when proposals are in the proposals queue
|
||||
@ -129,7 +127,7 @@ func main() {
|
||||
"pullCount": cfg.GetInt("queues.profiles.pullCount"),
|
||||
"query": "SPOP",
|
||||
"component": "statestorage",
|
||||
}).Info("Retreiving match profiles")
|
||||
}).Debug("Retreiving match profiles")
|
||||
|
||||
results, err := redis.Strings(redisConn.Do("SPOP",
|
||||
cfg.GetString("queues.profiles.name"), cfg.GetInt("queues.profiles.pullCount")))
|
||||
@ -144,14 +142,14 @@ func main() {
|
||||
|
||||
for _, profile := range results {
|
||||
// Kick off the job asynchrnously
|
||||
go mmfunc(ctx, profile, cfg, defaultMmfImages, clientset, pool)
|
||||
go mmfunc(ctx, profile, cfg, clientset, pool)
|
||||
// Count the number of jobs running
|
||||
redisHelpers.Increment(context.Background(), pool, "concurrentMMFs")
|
||||
}
|
||||
} else {
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"profileQueueName": cfg.GetString("queues.profiles.name"),
|
||||
}).Warn("Unable to retreive match profiles from statestorage - have you entered any?")
|
||||
}).Info("Unable to retreive match profiles from statestorage - have you entered any?")
|
||||
}
|
||||
|
||||
// Check to see if we should run the evaluator.
|
||||
@ -159,7 +157,6 @@ func main() {
|
||||
r, err := redisHelpers.Retrieve(context.Background(), pool, "concurrentMMFs")
|
||||
|
||||
if err != nil {
|
||||
mmforcLog.Println(err)
|
||||
if err.Error() == "redigo: nil returned" {
|
||||
// No MMFs have run since we last evaluated; reset timer and loop
|
||||
mmforcLog.Debug("Number of concurrentMMFs is nil")
|
||||
@ -184,7 +181,7 @@ func main() {
|
||||
// which have some overlap in the matchmaking player pools. Suffice to
|
||||
// say that under load, this switch should almost always trigger the
|
||||
// timeout interval code path. The concurrentMMFs check to see how
|
||||
// many are still running is meant as a short-circuit to prevent
|
||||
// many are still running is meant as a deadman's switch to prevent
|
||||
// waiting to run the evaluator when all your MMFs are already
|
||||
// finished.
|
||||
switch {
|
||||
@ -204,7 +201,8 @@ func main() {
|
||||
}
|
||||
|
||||
if checkProposals {
|
||||
// Make sure there are proposals in the queue.
|
||||
// Make sure there are proposals in the queue. No need to run the
|
||||
// evaluator if there are none.
|
||||
checkProposals = false
|
||||
mmforcLog.Info("Checking statestorage for match object proposals")
|
||||
results, err := redisHelpers.Count(context.Background(), pool, cfg.GetString("queues.proposals.name"))
|
||||
@ -219,7 +217,7 @@ func main() {
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"numProposals": results,
|
||||
}).Info("Proposals available, evaluating!")
|
||||
go evaluator(ctx, cfg, defaultEvalImage, clientset)
|
||||
go evaluator(ctx, cfg, clientset)
|
||||
}
|
||||
_, err = redisHelpers.Delete(context.Background(), pool, "concurrentMMFs")
|
||||
if err != nil {
|
||||
@ -242,110 +240,91 @@ func main() {
|
||||
}
|
||||
|
||||
// mmfunc generates a k8s job that runs the specified mmf container image.
|
||||
func mmfunc(ctx context.Context, profile string, cfg *viper.Viper, imageNames []string, clientset *kubernetes.Clientset, pool *redis.Pool) {
|
||||
// resultsID is the redis key that the Backend API is monitoring for results; we can 'short circuit' and write errors directly to this key if we can't run the MMF for some reason.
|
||||
func mmfunc(ctx context.Context, resultsID string, cfg *viper.Viper, clientset *kubernetes.Clientset, pool *redis.Pool) {
|
||||
|
||||
// Generate the various keys/names, some of which must be populated to the k8s job.
|
||||
ids := strings.Split(profile, ".")
|
||||
imageName := cfg.GetString("defaultImages.mmf.name") + ":" + cfg.GetString("defaultImages.mmf.tag")
|
||||
jobType := "mmf"
|
||||
ids := strings.Split(resultsID, ".") // comes in as dot-concatinated moID and profID.
|
||||
moID := ids[0]
|
||||
proID := ids[1]
|
||||
profID := ids[1]
|
||||
timestamp := strconv.Itoa(int(time.Now().Unix()))
|
||||
jobName := timestamp + "." + moID + "." + proID
|
||||
jobName := timestamp + "." + moID + "." + profID + "." + jobType
|
||||
propID := "proposal." + timestamp + "." + moID + "." + profID
|
||||
|
||||
// Read the full profile from redis and access any keys that are important to deciding how MMFs are run.
|
||||
profile, err := redisHelpers.Retrieve(ctx, pool, proID)
|
||||
if err != nil {
|
||||
// Note that we couldn't read the profile, and try to run the mmf with default settings.
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"jobName": moID,
|
||||
"profile": proID,
|
||||
"containerImages": imageNames,
|
||||
}).Warn("Failure retreiving full profile from statestorage - attempting to run default mmf container")
|
||||
} else {
|
||||
profileImageNames := gjson.Get(profile, cfg.GetString("jsonkeys.mmfImages"))
|
||||
|
||||
// Got profile from state storage, make sure it is valid
|
||||
if gjson.Valid(profile) && profileImageNames.Exists() {
|
||||
switch profileImageNames.Type.String() {
|
||||
case "String":
|
||||
// case: only one image name at this key.
|
||||
imageNames = []string{profileImageNames.String()}
|
||||
case "JSON":
|
||||
// case: Array of image names at this key.
|
||||
// TODO: support multiple MMFs per profile. Doing this will require that
|
||||
// we generate an proposal ID and populate it to the env vars for each
|
||||
// mmf, so they can each write a proposal for the same profile
|
||||
// without stomping each other. (The evaluator would then be
|
||||
// responsible for selecting the proposal to send to the backendapi)
|
||||
imageNames = []string{}
|
||||
|
||||
// Pattern for iterating through a gjson.Result
|
||||
// https://github.com/tidwall/gjson#iterate-through-an-object-or-array
|
||||
profileImageNames.ForEach(func(_, name gjson.Result) bool {
|
||||
// TODO: Swap these two lines when multiple image support is ready
|
||||
// imageNames = append(imageNames, name.String())
|
||||
imageNames = []string{name.String()}
|
||||
return true
|
||||
})
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"jobName": moID,
|
||||
"profile": proID,
|
||||
"containerImages": imageNames,
|
||||
}).Warn("Profile specifies multiple MMF container images (NYI), running only the last image provided")
|
||||
}
|
||||
} else {
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"jobName": moID,
|
||||
"profile": proID,
|
||||
"containerImages": imageNames,
|
||||
}).Warn("Profile JSON was invalid or did not contain a MMF container image name - attempting to run default mmf container")
|
||||
// Extra fields for structured logging
|
||||
lf := log.Fields{"jobName": jobName}
|
||||
if cfg.GetBool("debug") { // Log a lot more info.
|
||||
lf = log.Fields{
|
||||
"jobType": jobType,
|
||||
"backendMatchObject": moID,
|
||||
"profile": profID,
|
||||
"jobTimestamp": timestamp,
|
||||
"containerImage": imageName,
|
||||
"jobName": jobName,
|
||||
"profileImageJSONKey": cfg.GetString("jsonkeys.mmfImage"),
|
||||
}
|
||||
}
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"jobName": moID,
|
||||
"profile": proID,
|
||||
"containerImage": imageNames,
|
||||
}).Info("Attempting to create mmf k8s job")
|
||||
mmfuncLog := mmforcLog.WithFields(lf)
|
||||
|
||||
// Create Jobs
|
||||
// TODO: Handle returned errors
|
||||
// TODO: Support multiple MMFs per profile.
|
||||
// NOTE: For now, always send this an array of length 1 specifying the
|
||||
// single MMF container image name you want to run, until multi-mmf
|
||||
// profiles are supported. If you send it more than one, you will get
|
||||
// undefined (but definitely degenerate) behavior!
|
||||
for _, imageName := range imageNames {
|
||||
// Kick off Job with this image name
|
||||
_ = submitJob(imageName, jobName, clientset)
|
||||
if err != nil {
|
||||
// Record failure & log
|
||||
stats.Record(ctx, mmforcMmfFailures.M(1))
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"jobName": moID,
|
||||
"profile": proID,
|
||||
"containerImage": imageName,
|
||||
}).Error("MMF job submission failure!")
|
||||
// Read the full profile from redis and access any keys that are important to deciding how MMFs are run.
|
||||
// TODO: convert this to using redispb and directly access the protobuf message instead of retrieving as a map?
|
||||
profile, err := redisHelpers.RetrieveAll(ctx, pool, profID)
|
||||
if err != nil {
|
||||
// Log failure to read this profile and return - won't run an MMF for an unreadable profile.
|
||||
mmfuncLog.WithFields(log.Fields{"error": err.Error()}).Error("Failure retreiving profile from statestorage")
|
||||
return
|
||||
}
|
||||
|
||||
// Got profile from state storage, make sure it is valid
|
||||
if gjson.Valid(profile["properties"]) {
|
||||
profileImage := gjson.Get(profile["properties"], cfg.GetString("jsonkeys.mmfImage"))
|
||||
if profileImage.Exists() {
|
||||
imageName = profileImage.String()
|
||||
mmfuncLog = mmfuncLog.WithFields(log.Fields{"containerImage": imageName})
|
||||
} else {
|
||||
// Record Success
|
||||
stats.Record(ctx, mmforcMmfs.M(1))
|
||||
mmfuncLog.Warn("Failed to read image name from profile at configured json key, using default image instead")
|
||||
}
|
||||
}
|
||||
mmfuncLog.Info("Attempting to create mmf k8s job")
|
||||
|
||||
// Kick off k8s job
|
||||
envvars := []apiv1.EnvVar{
|
||||
{Name: "MMF_PROFILE_ID", Value: profID},
|
||||
{Name: "MMF_PROPOSAL_ID", Value: propID},
|
||||
{Name: "MMF_REQUEST_ID", Value: moID},
|
||||
{Name: "MMF_ERROR_ID", Value: resultsID},
|
||||
{Name: "MMF_TIMESTAMP", Value: timestamp},
|
||||
}
|
||||
err = submitJob(clientset, jobType, jobName, imageName, envvars)
|
||||
if err != nil {
|
||||
// Record failure & log
|
||||
stats.Record(ctx, mmforcMmfFailures.M(1))
|
||||
mmfuncLog.WithFields(log.Fields{"error": err.Error()}).Error("MMF job submission failure!")
|
||||
} else {
|
||||
// Record Success
|
||||
stats.Record(ctx, mmforcMmfs.M(1))
|
||||
}
|
||||
}
|
||||
|
||||
// evaluator generates a k8s job that runs the specified evaluator container image.
|
||||
func evaluator(ctx context.Context, cfg *viper.Viper, imageName string, clientset *kubernetes.Clientset) {
|
||||
func evaluator(ctx context.Context, cfg *viper.Viper, clientset *kubernetes.Clientset) {
|
||||
|
||||
imageName := cfg.GetString("defaultImages.evaluator.name") + ":" + cfg.GetString("defaultImages.evaluator.tag")
|
||||
// Generate the job name
|
||||
timestamp := strconv.Itoa(int(time.Now().Unix()))
|
||||
jobName := timestamp + ".evaluator"
|
||||
jobType := "evaluator"
|
||||
jobName := timestamp + "." + jobType
|
||||
|
||||
mmforcLog.WithFields(log.Fields{
|
||||
"jobName": jobName,
|
||||
"containerImage": imageName,
|
||||
}).Info("Attempting to create evaluator k8s job")
|
||||
|
||||
// Create Job
|
||||
// TODO: Handle returned errors
|
||||
_ = submitJob(imageName, jobName, clientset)
|
||||
// Kick off k8s job
|
||||
envvars := []apiv1.EnvVar{{Name: "MMF_TIMESTAMP", Value: timestamp}}
|
||||
err = submitJob(clientset, jobType, jobName, imageName, envvars)
|
||||
if err != nil {
|
||||
// Record failure & log
|
||||
stats.Record(ctx, mmforcEvalFailures.M(1))
|
||||
@ -361,8 +340,45 @@ func evaluator(ctx context.Context, cfg *viper.Viper, imageName string, clientse
|
||||
}
|
||||
|
||||
// submitJob submits a job to kubernetes
|
||||
func submitJob(imageName string, jobName string, clientset *kubernetes.Clientset) error {
|
||||
job := generateJobSpec(jobName, imageName)
|
||||
func submitJob(clientset *kubernetes.Clientset, jobType string, jobName string, imageName string, envvars []apiv1.EnvVar) error {
|
||||
|
||||
// DEPRECATED: will be removed in a future vrsion. Please switch to using the 'MMF_*' environment variables.
|
||||
v := strings.Split(jobName, ".")
|
||||
envvars = append(envvars, apiv1.EnvVar{Name: "PROFILE", Value: strings.Join(v[:len(v)-1], ".")})
|
||||
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: jobName,
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Completions: int32Ptr(1),
|
||||
Template: apiv1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": jobType,
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
// Unused; here as an example.
|
||||
// Later we can put things more complicated than
|
||||
// env vars here and read them using k8s downward API
|
||||
// volumes
|
||||
"profile": jobName,
|
||||
},
|
||||
},
|
||||
Spec: apiv1.PodSpec{
|
||||
RestartPolicy: "Never",
|
||||
Containers: []apiv1.Container{
|
||||
{
|
||||
Name: jobType,
|
||||
Image: imageName,
|
||||
ImagePullPolicy: "Always",
|
||||
Env: envvars,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Get the namespace for the job from the current namespace, otherwise, use default
|
||||
namespace := os.Getenv("METADATA_NAMESPACE")
|
||||
@ -387,56 +403,6 @@ func submitJob(imageName string, jobName string, clientset *kubernetes.Clientset
|
||||
return err
|
||||
}
|
||||
|
||||
// generateJobSpec is a PoC to test that all the k8s job generation code works.
|
||||
// In the future we should be decoding into the client object using one of the
|
||||
// codecs on an input JSON, or piggyback on job templates.
|
||||
// https://github.com/kubernetes/client-go/issues/193
|
||||
// TODO: many fields in this job spec assume the container image is an mmf, but
|
||||
// we use this to kick the evaluator containers too, should be updated to
|
||||
// reflect that
|
||||
func generateJobSpec(jobName string, imageName string) *batchv1.Job {
|
||||
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: jobName,
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Completions: int32Ptr(1),
|
||||
Template: apiv1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "mmf", // TODO: have this reflect mmf vs evaluator
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
// Unused; here as an example.
|
||||
// Later we can put params here and read them using the
|
||||
// k8s downward API volumes
|
||||
"profile": "exampleprofile",
|
||||
},
|
||||
},
|
||||
Spec: apiv1.PodSpec{
|
||||
RestartPolicy: "Never",
|
||||
Containers: []apiv1.Container{
|
||||
{
|
||||
// TODO: have these reflect mmf vs evaluator
|
||||
Name: "mmf",
|
||||
Image: imageName,
|
||||
ImagePullPolicy: "Always",
|
||||
Env: []apiv1.EnvVar{
|
||||
{
|
||||
Name: "PROFILE",
|
||||
Value: jobName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
// readability functions used by generateJobSpec
|
||||
func int32Ptr(i int32) *int32 { return &i }
|
||||
func strPtr(i string) *string { return &i }
|
||||
|
598
cmd/mmlogicapi/apisrv/apisrv.go
Normal file
598
cmd/mmlogicapi/apisrv/apisrv.go
Normal file
@ -0,0 +1,598 @@
|
||||
/*
|
||||
package apisrv provides an implementation of the gRPC server defined in ../../../api/protobuf-spec/mmlogic.proto.
|
||||
Most of the documentation for what these calls should do is in that file!
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
package apisrv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/metrics"
|
||||
mmlogic "github.com/GoogleCloudPlatform/open-match/internal/pb"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/set"
|
||||
redishelpers "github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/ignorelist"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/redispb"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/tag"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"go.opencensus.io/plugin/ocgrpc"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Logrus structured logging setup
|
||||
var (
|
||||
mlLogFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "mmlogic",
|
||||
"caller": "mmlogicapi/apisrv/apisrv.go",
|
||||
}
|
||||
mlLog = log.WithFields(mlLogFields)
|
||||
)
|
||||
|
||||
// MmlogicAPI implements mmlogic.ApiServer, the server generated by compiling
|
||||
// the protobuf, by fulfilling the mmlogic.APIClient interface.
|
||||
type MmlogicAPI struct {
|
||||
grpc *grpc.Server
|
||||
cfg *viper.Viper
|
||||
pool *redis.Pool
|
||||
}
|
||||
type mmlogicAPI MmlogicAPI
|
||||
|
||||
// New returns an instantiated srvice
|
||||
func New(cfg *viper.Viper, pool *redis.Pool) *MmlogicAPI {
|
||||
s := MmlogicAPI{
|
||||
pool: pool,
|
||||
grpc: grpc.NewServer(grpc.StatsHandler(&ocgrpc.ServerHandler{})),
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
// Add a hook to the logger to auto-count log lines for metrics output thru OpenCensus
|
||||
log.AddHook(metrics.NewHook(MlLogLines, KeySeverity))
|
||||
|
||||
// Register gRPC server
|
||||
mmlogic.RegisterMmLogicServer(s.grpc, (*mmlogicAPI)(&s))
|
||||
mlLog.Info("Successfully registered gRPC server")
|
||||
return &s
|
||||
}
|
||||
|
||||
// Open starts the api grpc service listening on the configured port.
|
||||
func (s *MmlogicAPI) Open() error {
|
||||
ln, err := net.Listen("tcp", ":"+s.cfg.GetString("api.mmlogic.port"))
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"port": s.cfg.GetInt("api.mmlogic.port"),
|
||||
}).Error("net.Listen() error")
|
||||
return err
|
||||
}
|
||||
mlLog.WithFields(log.Fields{"port": s.cfg.GetInt("api.mmlogic.port")}).Info("TCP net listener initialized")
|
||||
|
||||
go func() {
|
||||
err := s.grpc.Serve(ln)
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{"error": err.Error()}).Error("gRPC serve() error")
|
||||
}
|
||||
mlLog.Info("serving gRPC endpoints")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProfile is this service's implementation of the gRPC call defined in
|
||||
// mmlogicapi/proto/mmlogic.proto
|
||||
func (s *mmlogicAPI) GetProfile(c context.Context, profile *mmlogic.MatchObject) (*mmlogic.MatchObject, error) {
|
||||
|
||||
// Get redis connection from pool
|
||||
redisConn := s.pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "GetProfile"
|
||||
fnCtx, _ := tag.New(c, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
// Get profile.
|
||||
mlLog.WithFields(log.Fields{"profileid": profile.Id}).Info("Attempting retreival of profile")
|
||||
err := redispb.UnmarshalFromRedis(c, s.pool, profile)
|
||||
mlLog.Warn("returned profile from redispb", profile)
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
"profileid": profile.Id,
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return profile, err
|
||||
}
|
||||
mlLog.WithFields(log.Fields{"profileid": profile.Id}).Debug("Retrieved profile from state storage")
|
||||
|
||||
mlLog.Debug(profile)
|
||||
|
||||
stats.Record(fnCtx, MlGrpcRequests.M(1))
|
||||
//return out, err
|
||||
return profile, err
|
||||
|
||||
}
|
||||
|
||||
// CreateProposal is this service's implementation of the gRPC call defined in
|
||||
// mmlogicapi/proto/mmlogic.proto
|
||||
func (s *mmlogicAPI) CreateProposal(c context.Context, prop *mmlogic.MatchObject) (*mmlogic.Result, error) {
|
||||
|
||||
// Retreive configured redis keys.
|
||||
list := "proposed"
|
||||
proposalq := s.cfg.GetString("queues.proposals.name")
|
||||
|
||||
// Get redis connection from pool
|
||||
redisConn := s.pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "CreateProposal"
|
||||
fnCtx, _ := tag.New(c, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
// Log what kind of results we received.
|
||||
cpLog := mlLog.WithFields(log.Fields{"id": prop.Id})
|
||||
if len(prop.Error) == 0 {
|
||||
cpLog.Info("writing MMF propsal to state storage")
|
||||
} else {
|
||||
cpLog.Info("writing MMF error to state storage")
|
||||
}
|
||||
|
||||
// Write all non-id fields from the protobuf message to state storage.
|
||||
err := redispb.MarshalToRedis(c, prop, s.pool)
|
||||
if err != nil {
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return &mmlogic.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
|
||||
// Proposals need two more actions: players added to ignorelist, and adding
|
||||
// the proposalkey to the proposal queue for the evaluator to read.
|
||||
if len(prop.Error) == 0 {
|
||||
// look for players to add to the ignorelist
|
||||
cpLog.Info("parsing rosters")
|
||||
playerIDs := make([]string, 0)
|
||||
for _, roster := range prop.Rosters {
|
||||
playerIDs = append(playerIDs, getPlayerIdsFromRoster(roster)...)
|
||||
}
|
||||
|
||||
// If players were on the roster, add them to the ignorelist
|
||||
if len(playerIDs) > 0 {
|
||||
cpLog.WithFields(log.Fields{
|
||||
"count": len(playerIDs),
|
||||
"ignorelist": list,
|
||||
}).Info("adding players to ignorelist")
|
||||
|
||||
err := ignorelist.Add(redisConn, list, playerIDs)
|
||||
if err != nil {
|
||||
cpLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
"ignorelist": list,
|
||||
}).Error("State storage error")
|
||||
|
||||
// record error.
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return &mmlogic.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
} else {
|
||||
cpLog.Warn("found no players in rosters, not adding any players to the proposed ignorelist")
|
||||
}
|
||||
|
||||
// add propkey to proposalsq
|
||||
pqLog := cpLog.WithFields(log.Fields{
|
||||
"component": "statestorage",
|
||||
"queue": proposalq,
|
||||
})
|
||||
pqLog.Info("adding propsal to queue")
|
||||
|
||||
_, err = redisConn.Do("SADD", proposalq, prop.Id)
|
||||
if err != nil {
|
||||
pqLog.WithFields(log.Fields{"error": err.Error()}).Error("State storage error")
|
||||
|
||||
// record error.
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return &mmlogic.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Mark this MMF as finished by decrementing the concurrent MMFs.
|
||||
// This is used to trigger the evaluator early if all MMFs have finished
|
||||
// before its next scheduled run.
|
||||
cmLog := cpLog.WithFields(log.Fields{
|
||||
"component": "statestorage",
|
||||
"key": "concurrentMMFs",
|
||||
})
|
||||
cmLog.Info("marking MMF finished for evaluator")
|
||||
_, err = redishelpers.Decrement(fnCtx, s.pool, "concurrentMMFs")
|
||||
if err != nil {
|
||||
cmLog.WithFields(log.Fields{"error": err.Error()}).Error("State storage error")
|
||||
|
||||
// record error.
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return &mmlogic.Result{Success: false, Error: err.Error()}, err
|
||||
}
|
||||
|
||||
stats.Record(fnCtx, MlGrpcRequests.M(1))
|
||||
return &mmlogic.Result{Success: true, Error: ""}, err
|
||||
}
|
||||
|
||||
// GetPlayerPool is this service's implementation of the gRPC call defined in
|
||||
// mmlogicapi/proto/mmlogic.proto
|
||||
// API_GetPlayerPoolServer returns mutiple PlayerPool messages - they should
|
||||
// all be reassembled into one set on the calling side, as they are just
|
||||
// paginated subsets of the player pool.
|
||||
func (s *mmlogicAPI) GetPlayerPool(pool *mmlogic.PlayerPool, stream mmlogic.MmLogic_GetPlayerPoolServer) error {
|
||||
|
||||
// TODO: quit if context is cancelled
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "GetPlayerPool"
|
||||
fnCtx, _ := tag.New(ctx, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
mlLog.WithFields(log.Fields{
|
||||
"filterCount": len(pool.Filters),
|
||||
"pool": pool.Name,
|
||||
"funcName": funcName,
|
||||
}).Info("attempting to retreive player pool from state storage")
|
||||
|
||||
// One working Roster per filter in the set. Combined at the end.
|
||||
filteredRosters := make(map[string][]string)
|
||||
// Temp store the results so we can also populate some field values in the final return roster.
|
||||
filteredResults := make(map[string]map[string]int64)
|
||||
overlap := make([]string, 0)
|
||||
fnStart := time.Now()
|
||||
|
||||
// Loop over all filters, get results, combine
|
||||
for _, thisFilter := range pool.Filters {
|
||||
|
||||
filterStart := time.Now()
|
||||
results, err := s.applyFilter(ctx, thisFilter)
|
||||
thisFilter.Stats = &mmlogic.Stats{Count: int64(len(results)), Elapsed: time.Since(filterStart).Seconds()}
|
||||
mlLog.WithFields(log.Fields{
|
||||
"count": int64(len(results)),
|
||||
"elapsed": time.Since(filterStart).Seconds(),
|
||||
"filterName": thisFilter.Name,
|
||||
}).Debug("Filter stats")
|
||||
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{"error": err.Error(), "filterName": thisFilter.Name}).Debug("Error applying filter")
|
||||
|
||||
if len(results) == 0 {
|
||||
// One simple optimization here: check the count returned by a
|
||||
// ZCOUNT query for each filter before doing anything. If any of the
|
||||
// filters return a ZCOUNT of 0, then the logical AND of all filters will
|
||||
// container no players and we can shortcircuit and quit.
|
||||
mlLog.WithFields(log.Fields{
|
||||
"count": 0,
|
||||
"filterName": thisFilter.Name,
|
||||
"pool": pool.Name,
|
||||
}).Warn("returning empty pool")
|
||||
|
||||
// Fill in the stats for this player pool.
|
||||
pool.Stats = &mmlogic.Stats{Count: int64(len(results)), Elapsed: time.Since(filterStart).Seconds()}
|
||||
|
||||
// Send the empty pool and exit.
|
||||
if err = stream.Send(pool); err != nil {
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return err
|
||||
}
|
||||
stats.Record(fnCtx, MlGrpcRequests.M(1))
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Make an array of only the player IDs; used to do set.Unions and find the
|
||||
// logical AND
|
||||
m := make([]string, len(results))
|
||||
i := 0
|
||||
for playerID := range results {
|
||||
m[i] = playerID
|
||||
i++
|
||||
}
|
||||
|
||||
// Store the array of player IDs as well as the full results for later
|
||||
// retrieval
|
||||
filteredRosters[thisFilter.Attribute] = m
|
||||
filteredResults[thisFilter.Attribute] = results
|
||||
overlap = m
|
||||
}
|
||||
|
||||
// Player must be in every filtered pool to be returned
|
||||
for field, thesePlayers := range filteredRosters {
|
||||
overlap = set.Intersection(overlap, thesePlayers)
|
||||
|
||||
_ = field
|
||||
//mlLog.WithFields(log.Fields{"count": len(overlap), "field": field}).Debug("Amount of overlap")
|
||||
}
|
||||
|
||||
// Get contents of all ignore lists and remove those players from the pool.
|
||||
il, err := s.allIgnoreLists(ctx, &mmlogic.IlInput{})
|
||||
if err != nil {
|
||||
mlLog.Error(err)
|
||||
}
|
||||
mlLog.WithFields(log.Fields{"count": len(overlap)}).Debug("Pool size before applying ignorelists")
|
||||
mlLog.WithFields(log.Fields{"count": len(il)}).Debug("Ignorelist size")
|
||||
playerList := set.Difference(overlap, il) // removes ignorelist from the Roster
|
||||
mlLog.WithFields(log.Fields{"count": len(playerList)}).Debug("Final Pool size")
|
||||
|
||||
// Reformat the playerList as a gRPC PlayerPool message. Send partial results as we go.
|
||||
// This is pretty agressive in the partial result 'page'
|
||||
// sizes it sends, and that is partially because it assumes you're running
|
||||
// everything on a local network. If you aren't, you may need to tune this
|
||||
// pageSize.
|
||||
pageSize := s.cfg.GetInt("redis.results.pageSize")
|
||||
pageCount := int(math.Ceil((float64(len(playerList)) / float64(pageSize)))) // Divides and rounds up on any remainder
|
||||
//TODO: change if removing filtersets from rosters in favor of it being in pools
|
||||
partialRoster := mmlogic.Roster{Name: fmt.Sprintf("%v.partialRoster", pool.Name)}
|
||||
pool.Stats = &mmlogic.Stats{Count: int64(len(playerList)), Elapsed: time.Since(fnStart).Seconds()}
|
||||
for i := 0; i < len(playerList); i++ {
|
||||
// Check if we've filled in enough players to fill a page of results.
|
||||
if (i > 0 && i%pageSize == 0) || i == (len(playerList)-1) {
|
||||
pageName := fmt.Sprintf("%v.page%v/%v", pool.Name, i/pageSize+1, pageCount)
|
||||
poolChunk := &mmlogic.PlayerPool{
|
||||
Name: pageName,
|
||||
Filters: pool.Filters,
|
||||
Stats: pool.Stats,
|
||||
Roster: &partialRoster,
|
||||
}
|
||||
if err = stream.Send(poolChunk); err != nil {
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return err
|
||||
}
|
||||
partialRoster.Players = []*mmlogic.Player{}
|
||||
}
|
||||
|
||||
// Add one additional player result to the partial pool.
|
||||
player := &mmlogic.Player{Id: playerList[i], Attributes: []*mmlogic.Player_Attribute{}}
|
||||
// Collect all the filtered attributes into the player protobuf.
|
||||
for attribute, fr := range filteredResults {
|
||||
if value, ok := fr[playerList[i]]; ok {
|
||||
player.Attributes = append(player.Attributes, &mmlogic.Player_Attribute{Name: attribute, Value: value})
|
||||
}
|
||||
}
|
||||
partialRoster.Players = append(partialRoster.Players, player)
|
||||
|
||||
}
|
||||
|
||||
mlLog.WithFields(log.Fields{"count": len(playerList), "pool": pool.Name}).Debug("player pool streaming complete")
|
||||
|
||||
stats.Record(fnCtx, MlGrpcRequests.M(1))
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyFilter is a sequential query of every entry in the Redis sorted set
|
||||
// that fall beween the minimum and maximum values passed in through the filter
|
||||
// argument. This can be likely sped up later using concurrent access, but
|
||||
// with small enough player pools (less than the 'redis.queryArgs.count' config
|
||||
// parameter) the amount of work is identical, so this is fine as a starting point.
|
||||
// If the provided field is not indexed or the provided range is too large, a nil result
|
||||
// is returned and this filter should be disregarded when applying filter overlaps.
|
||||
func (s *mmlogicAPI) applyFilter(c context.Context, filter *mmlogic.Filter) (map[string]int64, error) {
|
||||
|
||||
type pName string
|
||||
pool := make(map[string]int64)
|
||||
|
||||
// Default maximum value is positive infinity (i.e. highest possible number in redis)
|
||||
// https://redis.io/commands/zrangebyscore
|
||||
maxv := strconv.FormatInt(filter.Maxv, 10) // Convert int64 to a string
|
||||
if filter.Maxv == 0 { // No max specified, set to +inf
|
||||
maxv = "+inf"
|
||||
}
|
||||
|
||||
mlLog.WithFields(log.Fields{"filterField": filter.Attribute}).Debug("In applyFilter")
|
||||
|
||||
// Get redis connection from pool
|
||||
redisConn := s.pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
// Check how many expected matches for this filter before we start retrieving.
|
||||
cmd := "ZCOUNT"
|
||||
count, err := redis.Int64(redisConn.Do(cmd, filter.Attribute, filter.Minv, maxv))
|
||||
//DEBUG: count, err := redis.Int64(redisConn.Do(cmd, "BLARG", filter.Minv, maxv))
|
||||
mlLog := mlLog.WithFields(log.Fields{
|
||||
"query": cmd,
|
||||
"field": filter.Attribute,
|
||||
"minv": filter.Minv,
|
||||
"maxv": maxv,
|
||||
"count": count,
|
||||
})
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{"error": err.Error()}).Error("state storage error")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
err = errors.New("filter applies to no players")
|
||||
mlLog.Error(err.Error())
|
||||
return nil, err
|
||||
} else if count > 500000 {
|
||||
// 500,000 results is an arbitrary number; OM doesn't encourage
|
||||
// patterns where MMFs look at this large of a pool.
|
||||
err = errors.New("filter applies to too many players")
|
||||
mlLog.Error(err.Error())
|
||||
for i := 0; i < int(count); i++ {
|
||||
// Send back an empty pool, used by the calling function to calculate the number of results
|
||||
pool[strconv.Itoa(i)] = 0
|
||||
}
|
||||
return pool, err
|
||||
} else if count < 100000 {
|
||||
mlLog.Info("filter processed")
|
||||
} else {
|
||||
// Send a warning to the logs.
|
||||
mlLog.Warn("filter applies to a large number of players")
|
||||
}
|
||||
|
||||
// Amount of results look okay and no redis error, begin
|
||||
// var init for player retrieval
|
||||
cmd = "ZRANGEBYSCORE"
|
||||
offset := 0
|
||||
|
||||
// Loop, retrieving players in chunks.
|
||||
for len(pool) == offset {
|
||||
results, err := redis.Int64Map(redisConn.Do(cmd, filter.Attribute, filter.Minv, maxv, "WITHSCORES", "LIMIT", offset, s.cfg.GetInt("redis.queryArgs.count")))
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{
|
||||
"query": cmd,
|
||||
"field": filter.Attribute,
|
||||
"minv": filter.Minv,
|
||||
"maxv": maxv,
|
||||
"offset": offset,
|
||||
"count": s.cfg.GetInt("redis.queryArgs.count"),
|
||||
"error": err.Error(),
|
||||
}).Error("statestorage error")
|
||||
}
|
||||
|
||||
// Increment the offset for the next query by the 'count' config value
|
||||
offset = offset + s.cfg.GetInt("redis.queryArgs.count")
|
||||
|
||||
// Add all results to this player pool
|
||||
for k, v := range results {
|
||||
if _, ok := pool[k]; ok {
|
||||
// Redis returned the same player more than once; this is not
|
||||
// actually a problem, it just indicates that players are being
|
||||
// added/removed from the index as it is queried. We take the
|
||||
// tradeoff in consistency for speed, as it won't cause issues
|
||||
// in matchmaking results as long as ignorelists are respected.
|
||||
offset--
|
||||
}
|
||||
pool[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Log completion and return
|
||||
//mlLog.WithFields(log.Fields{
|
||||
// "poolSize": len(pool),
|
||||
// "field": filter.Attribute,
|
||||
// "minv": filter.Minv,
|
||||
// "maxv": maxv,
|
||||
//}).Debug("Player pool filter processed")
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// GetAllIgnoredPlayers is this service's implementation of the gRPC call defined in
|
||||
// mmlogicapi/proto/mmlogic.proto
|
||||
// This is a wrapper around allIgnoreLists, and converts the []string return
|
||||
// value of that function to a gRPC Roster message to send out over the wire.
|
||||
func (s *mmlogicAPI) GetAllIgnoredPlayers(c context.Context, in *mmlogic.IlInput) (*mmlogic.Roster, error) {
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "GetAllIgnoredPlayers"
|
||||
fnCtx, _ := tag.New(c, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
il, err := s.allIgnoreLists(c, in)
|
||||
|
||||
stats.Record(fnCtx, MlGrpcRequests.M(1))
|
||||
return createRosterfromPlayerIds(il), err
|
||||
}
|
||||
|
||||
// ListIgnoredPlayers is this service's implementation of the gRPC call defined in
|
||||
// mmlogicapi/proto/mmlogic.proto
|
||||
func (s *mmlogicAPI) ListIgnoredPlayers(c context.Context, olderThan *mmlogic.IlInput) (*mmlogic.Roster, error) {
|
||||
|
||||
// TODO: is this supposed to able to take any list?
|
||||
ilName := "proposed"
|
||||
|
||||
// Get redis connection from pool
|
||||
redisConn := s.pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
// Create context for tagging OpenCensus metrics.
|
||||
funcName := "ListIgnoredPlayers"
|
||||
fnCtx, _ := tag.New(c, tag.Insert(KeyMethod, funcName))
|
||||
|
||||
mlLog.WithFields(log.Fields{"ignorelist": ilName}).Info("Attempting to get ignorelist")
|
||||
|
||||
// retreive ignore list
|
||||
il, err := ignorelist.Retrieve(redisConn, s.cfg, ilName)
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
"key": ilName,
|
||||
}).Error("State storage error")
|
||||
|
||||
stats.Record(fnCtx, MlGrpcErrors.M(1))
|
||||
return &mmlogic.Roster{}, err
|
||||
}
|
||||
// TODO: fix this
|
||||
mlLog.Debug(fmt.Sprintf("Retreival success %v", il))
|
||||
|
||||
stats.Record(fnCtx, MlGrpcRequests.M(1))
|
||||
return createRosterfromPlayerIds(il), err
|
||||
}
|
||||
|
||||
// allIgnoreLists combines all the ignore lists and returns them.
|
||||
func (s *mmlogicAPI) allIgnoreLists(c context.Context, in *mmlogic.IlInput) (allIgnored []string, err error) {
|
||||
|
||||
// Get redis connection from pool
|
||||
redisConn := s.pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
mlLog.Info("Attempting to get and combine ignorelists")
|
||||
|
||||
// Loop through all ignorelists configured in the config file.
|
||||
for il := range s.cfg.GetStringMap("ignoreLists") {
|
||||
ilCfg := s.cfg.Sub(fmt.Sprintf("ignoreLists.%v", il))
|
||||
thisIl, err := ignorelist.Retrieve(redisConn, ilCfg, il)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Join this ignorelist to the others we've retrieved
|
||||
allIgnored = set.Union(allIgnored, thisIl)
|
||||
}
|
||||
|
||||
return allIgnored, err
|
||||
}
|
||||
|
||||
// Functions for getting or setting player IDs to/from rosters
|
||||
// Probably should get moved to an internal module in a future version.
|
||||
func getPlayerIdsFromRoster(r *mmlogic.Roster) []string {
|
||||
playerIDs := make([]string, 0)
|
||||
for _, p := range r.Players {
|
||||
playerIDs = append(playerIDs, p.Id)
|
||||
}
|
||||
return playerIDs
|
||||
|
||||
}
|
||||
|
||||
func createRosterfromPlayerIds(playerIDs []string) *mmlogic.Roster {
|
||||
|
||||
players := make([]*mmlogic.Player, 0)
|
||||
for _, id := range playerIDs {
|
||||
players = append(players, &mmlogic.Player{Id: id})
|
||||
}
|
||||
return &mmlogic.Roster{Players: players}
|
||||
|
||||
}
|
139
cmd/mmlogicapi/apisrv/mmlogic_stats.go
Normal file
139
cmd/mmlogicapi/apisrv/mmlogic_stats.go
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package apisrv
|
||||
|
||||
import (
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
// OpenCensus Measures. These are exported as metrics to your monitoring system
|
||||
// https://godoc.org/go.opencensus.io/stats
|
||||
//
|
||||
// When making opencensus stats, the 'name' param, with forward slashes changed
|
||||
// to underscores, is appended to the 'namespace' value passed to the
|
||||
// prometheus exporter to become the Prometheus metric name. You can also look
|
||||
// into having Prometheus rewrite your metric names on scrape.
|
||||
//
|
||||
// For example:
|
||||
// - defining the promethus export namespace "open_match" when instanciating the exporter:
|
||||
// pe, err := promethus.NewExporter(promethus.Options{Namespace: "open_match"})
|
||||
// - and naming the request counter "mmlogic/requests_total":
|
||||
// MGrpcRequests := stats.Int64("mmlogicapi/requests_total", ...
|
||||
// - results in the prometheus metric name:
|
||||
// open_match_mmlogicapi_requests_total
|
||||
// - [note] when using opencensus views to aggregate the metrics into
|
||||
// distribution buckets and such, multiple metrics
|
||||
// will be generated with appended types ("<metric>_bucket",
|
||||
// "<metric>_count", "<metric>_sum", for example)
|
||||
//
|
||||
// In addition, OpenCensus stats propogated to Prometheus have the following
|
||||
// auto-populated labels pulled from kubernetes, which we should avoid to
|
||||
// prevent overloading and having to use the HonorLabels param in Prometheus.
|
||||
//
|
||||
// - Information about the k8s pod being monitored:
|
||||
// "pod" (name of the monitored k8s pod)
|
||||
// "namespace" (k8s namespace of the monitored pod)
|
||||
// - Information about how promethus is gathering the metrics:
|
||||
// "instance" (IP and port number being scraped by prometheus)
|
||||
// "job" (name of the k8s service being scraped by prometheus)
|
||||
// "endpoint" (name of the k8s port in the k8s service being scraped by prometheus)
|
||||
//
|
||||
var (
|
||||
// API instrumentation
|
||||
MlGrpcRequests = stats.Int64("mmlogicapi/requests_total", "Number of requests to the gRPC Frontend API endpoints", "1")
|
||||
MlGrpcErrors = stats.Int64("mmlogicapi/errors_total", "Number of errors generated by the gRPC Frontend API endpoints", "1")
|
||||
MlGrpcLatencySecs = stats.Float64("mmlogicapi/latency_seconds", "Latency in seconds of the gRPC Frontend API endpoints", "1")
|
||||
|
||||
// Logging instrumentation
|
||||
// There's no need to record this measurement directly if you use
|
||||
// the logrus hook provided in metrics/helper.go after instantiating the
|
||||
// logrus instance in your application code.
|
||||
// https://godoc.org/github.com/sirupsen/logrus#LevelHooks
|
||||
MlLogLines = stats.Int64("mmlogicapi/logs_total", "Number of Frontend API lines logged", "1")
|
||||
|
||||
// Failure instrumentation
|
||||
MlFailures = stats.Int64("mmlogicapi/failures_total", "Number of Frontend API failures", "1")
|
||||
)
|
||||
|
||||
var (
|
||||
// KeyMethod is used to tag a measure with the currently running API method.
|
||||
KeyMethod, _ = tag.NewKey("method")
|
||||
KeySeverity, _ = tag.NewKey("severity")
|
||||
)
|
||||
|
||||
var (
|
||||
// Latency in buckets:
|
||||
// [>=0ms, >=25ms, >=50ms, >=75ms, >=100ms, >=200ms, >=400ms, >=600ms, >=800ms, >=1s, >=2s, >=4s, >=6s]
|
||||
latencyDistribution = view.Distribution(0, 25, 50, 75, 100, 200, 400, 600, 800, 1000, 2000, 4000, 6000)
|
||||
)
|
||||
|
||||
// Package metrics provides some convience views.
|
||||
// You need to register the views for the data to actually be collected.
|
||||
// Note: The OpenCensus View 'Description' is exported to Prometheus as the HELP string.
|
||||
// Note: If you get a "Failed to export to Prometheus: inconsistent label
|
||||
// cardinality" error, chances are you forgot to set the tags specified in the
|
||||
// view for a given measure when you tried to do a stats.Record()
|
||||
var (
|
||||
MlLatencyView = &view.View{
|
||||
Name: "mmlogic/latency",
|
||||
Measure: MlGrpcLatencySecs,
|
||||
Description: "The distribution of mmlogic latencies",
|
||||
Aggregation: latencyDistribution,
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
MlRequestCountView = &view.View{
|
||||
Name: "mmlogic/grpc/requests",
|
||||
Measure: MlGrpcRequests,
|
||||
Description: "The number of successful mmlogic gRPC requests",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
MlErrorCountView = &view.View{
|
||||
Name: "mmlogic/grpc/errors",
|
||||
Measure: MlGrpcErrors,
|
||||
Description: "The number of gRPC errors",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeyMethod},
|
||||
}
|
||||
|
||||
MlLogCountView = &view.View{
|
||||
Name: "log_lines/total",
|
||||
Measure: MlLogLines,
|
||||
Description: "The number of lines logged",
|
||||
Aggregation: view.Count(),
|
||||
TagKeys: []tag.Key{KeySeverity},
|
||||
}
|
||||
|
||||
MlFailureCountView = &view.View{
|
||||
Name: "failures",
|
||||
Measure: MlFailures,
|
||||
Description: "The number of failures",
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultMmlogicAPIViews are the default mmlogic API OpenCensus measure views.
|
||||
var DefaultMmlogicAPIViews = []*view.View{
|
||||
MlLatencyView,
|
||||
MlRequestCountView,
|
||||
MlErrorCountView,
|
||||
MlLogCountView,
|
||||
MlFailureCountView,
|
||||
}
|
105
cmd/mmlogicapi/main.go
Normal file
105
cmd/mmlogicapi/main.go
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
This application handles all the startup and connection scaffolding for
|
||||
running a gRPC server serving the APIService as defined in
|
||||
mmlogic/proto/mmlogic.pb.go
|
||||
|
||||
All the actual important bits are in the API Server source code: apisrv/apisrv.go
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/GoogleCloudPlatform/open-match/cmd/mmlogicapi/apisrv"
|
||||
"github.com/GoogleCloudPlatform/open-match/config"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/metrics"
|
||||
redisHelpers "github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
"go.opencensus.io/plugin/ocgrpc"
|
||||
)
|
||||
|
||||
var (
|
||||
// Logrus structured logging setup
|
||||
mlLogFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "mmlogic",
|
||||
"caller": "mmlogicapi/main.go",
|
||||
}
|
||||
mlLog = log.WithFields(mlLogFields)
|
||||
|
||||
// Viper config management setup
|
||||
cfg = viper.New()
|
||||
err = errors.New("")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Logrus structured logging initialization
|
||||
// Add a hook to the logger to auto-count log lines for metrics output thru OpenCensus
|
||||
log.AddHook(metrics.NewHook(apisrv.MlLogLines, apisrv.KeySeverity))
|
||||
|
||||
// Viper config management initialization
|
||||
cfg, err = config.Read()
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
}).Error("Unable to load config file")
|
||||
}
|
||||
|
||||
if cfg.GetBool("debug") == true {
|
||||
log.SetLevel(log.DebugLevel) // debug only, verbose - turn off in production!
|
||||
mlLog.Warn("Debug logging configured. Not recommended for production!")
|
||||
}
|
||||
|
||||
// Configure OpenCensus exporter to Prometheus
|
||||
// metrics.ConfigureOpenCensusPrometheusExporter expects that every OpenCensus view you
|
||||
// want to register is in an array, so append any views you want from other
|
||||
// packages to a single array here.
|
||||
ocServerViews := apisrv.DefaultMmlogicAPIViews // Matchmaking logic API OpenCensus views.
|
||||
ocServerViews = append(ocServerViews, ocgrpc.DefaultServerViews...) // gRPC OpenCensus views.
|
||||
ocServerViews = append(ocServerViews, config.CfgVarCountView) // config loader view.
|
||||
// Waiting on https://github.com/opencensus-integrations/redigo/pull/1
|
||||
// ocServerViews = append(ocServerViews, redis.ObservabilityMetricViews...) // redis OpenCensus views.
|
||||
mlLog.WithFields(log.Fields{"viewscount": len(ocServerViews)}).Info("Loaded OpenCensus views")
|
||||
metrics.ConfigureOpenCensusPrometheusExporter(cfg, ocServerViews)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// Connect to redis
|
||||
pool := redisHelpers.ConnectionPool(cfg)
|
||||
defer pool.Close()
|
||||
|
||||
// Instantiate the gRPC server with the connections we've made
|
||||
mlLog.WithFields(log.Fields{"testfield": "test"}).Info("Attempting to start gRPC server")
|
||||
srv := apisrv.New(cfg, pool)
|
||||
|
||||
// Run the gRPC server
|
||||
err := srv.Open()
|
||||
if err != nil {
|
||||
mlLog.WithFields(log.Fields{"error": err.Error()}).Fatal("Failed to start gRPC server")
|
||||
}
|
||||
|
||||
// Exit when we see a signal
|
||||
terminate := make(chan os.Signal, 1)
|
||||
signal.Notify(terminate, os.Interrupt)
|
||||
<-terminate
|
||||
mlLog.Info("Shutting down gRPC server")
|
||||
}
|
1
cmd/mmlogicapi/matchmaker_config.json
Symbolic link
1
cmd/mmlogicapi/matchmaker_config.json
Symbolic link
@ -0,0 +1 @@
|
||||
../../config/matchmaker_config.json
|
@ -2,10 +2,16 @@
|
||||
"debug": true,
|
||||
"api": {
|
||||
"backend": {
|
||||
"hostname": "om-backendapi",
|
||||
"port": 50505
|
||||
},
|
||||
"frontend": {
|
||||
"hostname": "om-frontendapi",
|
||||
"port": 50504
|
||||
},
|
||||
"mmlogic": {
|
||||
"hostname": "om-mmlogicapi",
|
||||
"port": 50503
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
@ -22,6 +28,23 @@
|
||||
"name": "proposalq"
|
||||
}
|
||||
},
|
||||
"ignoreLists": {
|
||||
"proposed": {
|
||||
"name": "proposed",
|
||||
"offset": 0,
|
||||
"duration": 800
|
||||
},
|
||||
"deindexed": {
|
||||
"name": "deindexed",
|
||||
"offset": 0,
|
||||
"duration": 800
|
||||
},
|
||||
"expired": {
|
||||
"name": "timestamp",
|
||||
"offset": 800,
|
||||
"duration": 0
|
||||
}
|
||||
},
|
||||
"defaultImages": {
|
||||
"evaluator": {
|
||||
"name": "gcr.io/matchmaker-dev-201405/openmatch-evaluator",
|
||||
@ -29,7 +52,7 @@
|
||||
},
|
||||
"mmf": {
|
||||
"name": "gcr.io/matchmaker-dev-201405/openmatch-mmf",
|
||||
"tag": "dev"
|
||||
"tag": "py3"
|
||||
}
|
||||
},
|
||||
"redis": {
|
||||
@ -39,15 +62,40 @@
|
||||
"maxIdle" : 3,
|
||||
"maxActive" : 0,
|
||||
"idleTimeout" : 60
|
||||
},
|
||||
"queryArgs":{
|
||||
"count": 10000
|
||||
},
|
||||
"results": {
|
||||
"pageSize": 10000
|
||||
}
|
||||
},
|
||||
"jsonkeys": {
|
||||
"mmfImages": "imagename",
|
||||
"roster": "profile.roster",
|
||||
"connstring": "connstring"
|
||||
"mmfImage": "imagename",
|
||||
"rosters": "properties.rosters",
|
||||
"connstring": "connstring",
|
||||
"pools": "properties.pools"
|
||||
},
|
||||
"interval": {
|
||||
"evaluator": 10,
|
||||
"resultsTimeout": 30
|
||||
}
|
||||
},
|
||||
"playerIndices": [
|
||||
"char.cleric",
|
||||
"char.knight",
|
||||
"char.paladin",
|
||||
"map.aleroth",
|
||||
"map.oasis",
|
||||
"mmr.rating",
|
||||
"mode.battleroyale",
|
||||
"mode.ctf",
|
||||
"region.europe-east1",
|
||||
"region.europe-west1",
|
||||
"region.europe-west2",
|
||||
"region.europe-west3",
|
||||
"region.europe-west4",
|
||||
"role.dps",
|
||||
"role.support",
|
||||
"role.tank"
|
||||
]
|
||||
}
|
||||
|
53
deployments/k8s/mmlogicapi_deployment.json
Normal file
53
deployments/k8s/mmlogicapi_deployment.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"apiVersion":"extensions/v1beta1",
|
||||
"kind":"Deployment",
|
||||
"metadata":{
|
||||
"name":"om-mmlogicapi",
|
||||
"labels":{
|
||||
"app":"openmatch",
|
||||
"component": "mmlogic"
|
||||
}
|
||||
},
|
||||
"spec":{
|
||||
"replicas":1,
|
||||
"selector":{
|
||||
"matchLabels":{
|
||||
"app":"openmatch",
|
||||
"component": "mmlogic"
|
||||
}
|
||||
},
|
||||
"template":{
|
||||
"metadata":{
|
||||
"labels":{
|
||||
"app":"openmatch",
|
||||
"component": "mmlogic"
|
||||
}
|
||||
},
|
||||
"spec":{
|
||||
"containers":[
|
||||
{
|
||||
"name":"om-mmlogic",
|
||||
"image":"gcr.io/matchmaker-dev-201405/openmatch-mmlogicapi:dev",
|
||||
"imagePullPolicy":"Always",
|
||||
"ports": [
|
||||
{
|
||||
"name": "grpc",
|
||||
"containerPort": 50503
|
||||
},
|
||||
{
|
||||
"name": "metrics",
|
||||
"containerPort": 9555
|
||||
}
|
||||
],
|
||||
"resources":{
|
||||
"requests":{
|
||||
"memory":"100Mi",
|
||||
"cpu":"100m"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
deployments/k8s/mmlogicapi_service.json
Normal file
20
deployments/k8s/mmlogicapi_service.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "om-mmlogicapi"
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"app": "openmatch",
|
||||
"component": "mmlogic"
|
||||
},
|
||||
"ports": [
|
||||
{
|
||||
"protocol": "TCP",
|
||||
"port": 50503,
|
||||
"targetPort": "grpc"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -4,17 +4,58 @@ All components of Open Match produce (Linux) Docker container images as artifact
|
||||
|
||||
Note: Although Google Cloud Platform includes some free usage, you may incur charges following this guide if you use GCP products.
|
||||
|
||||
**This project has not completed a first-line security audit, and there are definitely going to be some service accounts that are too permissive. This should be fine for testing/development in a local environment, but absolutely should not be used as-is in a production environment.**
|
||||
## Security Disclaimer
|
||||
**This project has not completed a first-line security audit, and there are definitely going to be some service accounts that are too permissive. This should be fine for testing/development in a local environment, but absolutely should not be used as-is in a production environment without your team/organization evaluating it's permissions.**
|
||||
|
||||
## Before getting started
|
||||
**NOTE**: Before starting with this guide, you'll need to update all the URIs from the tutorial's gcr.io container image registry with the URI for your own image registry. If you are using the gcr.io registry on GCP, the default URI is `gcr.io/<PROJECT_NAME>`. Here's an example command in Linux to do the replacement for you this (replace YOUR_REGISTRY_URI with your URI, this should be run from the repository root directory):
|
||||
```
|
||||
# Linux
|
||||
egrep -lR 'gcr.io/matchmaker-dev-201405' . | xargs sed -i -e 's|gcr.io/matchmaker-dev-201405|YOUR_REGISTRY_URI|g'
|
||||
```
|
||||
```
|
||||
# Mac OS, you can delete the .backup files after if all looks good
|
||||
egrep -lR 'gcr.io/matchmaker-dev-201405' . | xargs sed -i'.backup' -e 's|gcr.io/matchmaker-dev-201405|YOUR_REGISTRY_URI|g'
|
||||
```
|
||||
|
||||
## Example of building using Google Cloud Builder
|
||||
|
||||
The [Quickstart for Docker](https://cloud.google.com/cloud-build/docs/quickstart-docker) guide explains how to set up a project, enable billing, enable Cloud Build, and install the Cloud SDK if you haven't do these things before. Once you get to 'Preparing source files' you are ready to continue with the steps below.
|
||||
|
||||
* Clone this repo to a local machine or Google Cloud Shell session, and cd into it.
|
||||
* Run the following one-line bash script to compile all the images for the first time, and push them to your gcr.io registry. You must enable the [Container Registry API](https://console.cloud.google.com/flows/enableapi?apiid=containerregistry.googleapis.com) first.
|
||||
```
|
||||
for dfile in $(ls Dockerfile.*); do gcloud builds submit --config cloudbuild_${dfile##*.}.yaml; done
|
||||
```
|
||||
* In Linux, you can run the following one-line bash script to compile all the images for the first time, and push them to your gcr.io registry. You must enable the [Container Registry API](https://console.cloud.google.com/flows/enableapi?apiid=containerregistry.googleapis.com) first.
|
||||
```
|
||||
# First, build the 'base' image. Some other images depend on this so it must complete first.
|
||||
gcloud build submit --config cloudbuild_base.yaml
|
||||
# Build all other images.
|
||||
for dfile in $(ls Dockerfile.* | grep -v base); do gcloud builds submit --config cloudbuild_${dfile##*.}.yaml & done
|
||||
```
|
||||
* Once the cloud builds have completed, you can verify that all the builds succeeded in the cloud console or by by checking the list of images in your **gcr.io** registry:
|
||||
```
|
||||
gcloud container images list
|
||||
```
|
||||
(your registry name will be different)
|
||||
```
|
||||
NAME
|
||||
gcr.io/matchmaker-dev-201405/openmatch-backendapi
|
||||
gcr.io/matchmaker-dev-201405/openmatch-devbase
|
||||
gcr.io/matchmaker-dev-201405/openmatch-evaluator
|
||||
gcr.io/matchmaker-dev-201405/openmatch-frontendapi
|
||||
gcr.io/matchmaker-dev-201405/openmatch-mmf
|
||||
gcr.io/matchmaker-dev-201405/openmatch-mmforc
|
||||
gcr.io/matchmaker-dev-201405/openmatch-mmlogicapi
|
||||
```
|
||||
* The default example MMF images all use the same name (`openmatch-mmf`), with different image tags designating the different examples. You can check that these exist by running this command (again, substituting your **gcr.io** registry):
|
||||
```
|
||||
gcloud container images list-tags gcr.io/matchmaker-dev-201405/openmatch-mmf
|
||||
```
|
||||
You should see tags for several of the example MMFs. By default, Open Match will try to use the `openmatch-mmf:py3` image in the examples below, so it is important that the image build was successful and a `py3` image tag exists in your **gcr.io** registry before you continue:
|
||||
```
|
||||
DIGEST TAGS TIMESTAMP
|
||||
5345475e026c php 2018-12-05T00:06:47
|
||||
e5c274c3509c go 2018-12-05T00:02:17
|
||||
1b3ec3176d0f py3 2018-12-05T00:02:07
|
||||
```
|
||||
|
||||
## Example of starting a GKE cluster
|
||||
|
||||
@ -32,76 +73,115 @@ gcloud compute zones list
|
||||
|
||||
## Configuration
|
||||
|
||||
Currently, each component reads a local config file `matchmaker_config.json` , and all components assume they have the same configuration. To this end, there is a single centralized config file located in the `<REPO_ROOT>/config/` which is symlinked to each component's subdirectory for convenience when building locally.
|
||||
|
||||
**NOTE** 'defaultImages' container images names in the config file will need to be updated with **your container registry URI**. Here's an example command in Linux to do this (just replace YOUR_REGISTRY_URI with the appropriate location in your environment, should be run from the config directory):
|
||||
```
|
||||
sed -i 's|gcr.io/matchmaker-dev-201405|YOUR_REGISTRY_URI|g' matchmaker_config.json
|
||||
```
|
||||
For MacOS the `-i` flag creates backup files when changing the original file in place. You can use the following command, and then delete the `*.backup` files afterwards if you don't need them anymore:
|
||||
```
|
||||
sed -i'.backup' -e 's|gcr.io/matchmaker-dev-201405|YOUR_REGISTRY_URI|g' matchmaker_config.json
|
||||
```
|
||||
If you are using the gcr.io registry on GCP, the default URI is `gcr.io/<PROJECT_NAME>`.
|
||||
|
||||
We plan to replace this with a Kubernetes-managed config with dynamic reloading when development time allows. Pull requests are welcome!
|
||||
Currently, each component reads a local config file `matchmaker_config.json`, and all components assume they have the same configuration (if you would like to help us design the replacement config solution, please join the [discussion](https://github.com/GoogleCloudPlatform/open-match/issues/42). To this end, there is a single centralized config file located in the `<REPO_ROOT>/config/` which is symlinked to each component's subdirectory for convenience when building locally.
|
||||
|
||||
## Running Open Match in a development environment
|
||||
|
||||
The rest of this guide assumes you have a cluster (example is using GKE, but works on any cluster with a little tweaking), and kubectl configured to administer that cluster, and you've built all the Docker container images described by `Dockerfiles` in the repository root directory and given them the docker tag 'dev'. It assumes you are in the `<REPO_ROOT>/deployments/k8s/` directory.
|
||||
|
||||
**NOTE** Kubernetes resources that use container images will need to be updated with **your container registry URI**. Here's an example command in Linux to do this (just replace YOUR_REGISTRY_URI with the appropriate location in your environment):
|
||||
```
|
||||
sed -i 's|gcr.io/matchmaker-dev-201405|YOUR_REGISTRY_URI|g' *deployment.json
|
||||
```
|
||||
For MacOS the `-i` flag creates backup files when changing the original file in place. You can use the following command, and then delete the `*.backup` files afterwards if you don't need them anymore:
|
||||
```
|
||||
sed -i'.backup' -e 's|gcr.io/matchmaker-dev-201405|YOUR_REGISTRY_URI|g' *deployment.json
|
||||
```
|
||||
If you are using the gcr.io registry on GCP, the default URI is `gcr.io/<PROJECT_NAME>`.
|
||||
|
||||
* Start a copy of redis and a service in front of it:
|
||||
```
|
||||
kubectl apply -f redis_deployment.json
|
||||
kubectl apply -f redis_service.json
|
||||
```
|
||||
* Run the **core components**: the frontend API, the backend API, and the matchmaker function orchestrator (MMFOrc).
|
||||
```
|
||||
kubectl apply -f redis_deployment.json
|
||||
kubectl apply -f redis_service.json
|
||||
```
|
||||
* Run the **core components**: the frontend API, the backend API, the matchmaker function orchestrator (MMFOrc), and the matchmaking logic API.
|
||||
**NOTE** In order to kick off jobs, the matchmaker function orchestrator needs a service account with permission to administer the cluster. This should be updated to have min required perms before launch, this is pretty permissive but acceptable for closed testing:
|
||||
```
|
||||
kubectl apply -f backendapi_deployment.json
|
||||
kubectl apply -f backendapi_service.json
|
||||
kubectl apply -f frontendapi_deployment.json
|
||||
kubectl apply -f frontendapi_service.json
|
||||
kubectl apply -f mmforc_deployment.json
|
||||
kubectl apply -f mmforc_serviceaccount.json
|
||||
```
|
||||
```
|
||||
kubectl apply -f backendapi_deployment.json
|
||||
kubectl apply -f backendapi_service.json
|
||||
kubectl apply -f frontendapi_deployment.json
|
||||
kubectl apply -f frontendapi_service.json
|
||||
kubectl apply -f mmforc_deployment.json
|
||||
kubectl apply -f mmforc_serviceaccount.json
|
||||
kubectl apply -f mmlogic_deployment.json
|
||||
kubectl apply -f mmlogic_service.json
|
||||
```
|
||||
* [optional, but recommended] Configure the OpenCensus metrics services:
|
||||
```
|
||||
kubectl apply -f metrics_services.json
|
||||
```
|
||||
```
|
||||
kubectl apply -f metrics_services.json
|
||||
```
|
||||
* [optional] Trying to apply the Kubernetes Prometheus Operator resource definition files without a cluster-admin rolebinding on GKE doesn't work without running the following command first. See https://github.com/coreos/prometheus-operator/issues/357
|
||||
```
|
||||
kubectl create clusterrolebinding projectowner-cluster-admin-binding --clusterrole=cluster-admin --user=<GCP_ACCOUNT>
|
||||
```
|
||||
```
|
||||
kubectl create clusterrolebinding projectowner-cluster-admin-binding --clusterrole=cluster-admin --user=<GCP_ACCOUNT>
|
||||
```
|
||||
* [optional, uses beta software] If using Prometheus as your metrics gathering backend, configure the [Prometheus Kubernetes Operator](https://github.com/coreos/prometheus-operator):
|
||||
|
||||
```
|
||||
kubectl apply -f prometheus_operator.json
|
||||
kubectl apply -f prometheus.json
|
||||
kubectl apply -f prometheus_service.json
|
||||
kubectl apply -f metrics_servicemonitor.json
|
||||
```
|
||||
|
||||
```
|
||||
kubectl apply -f prometheus_operator.json
|
||||
kubectl apply -f prometheus.json
|
||||
kubectl apply -f prometheus_service.json
|
||||
kubectl apply -f metrics_servicemonitor.json
|
||||
```
|
||||
You should now be able to see the core component pods running using a `kubectl get pods`, and the core component metrics in the Prometheus Web UI by running `kubectl proxy <PROMETHEUS_POD_NAME> 9090:9090` in your local shell, then opening http://localhost:9090/targets in your browser to see which services Prometheus is collecting from.
|
||||
|
||||
Here's an example output from `kubectl get all` if everything started correctly, and you included all the optional components (note: this could become out-of-date with upcoming versions; apologies if that happens):
|
||||
```
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
pod/om-backendapi-84bc9d8fff-q89kr 1/1 Running 0 9m
|
||||
pod/om-frontendapi-55d5bb7946-c5ccb 1/1 Running 0 9m
|
||||
pod/om-mmforc-85bfd7f4f6-wmwhc 1/1 Running 0 9m
|
||||
pod/om-mmlogicapi-6488bc7fc6-g74dm 1/1 Running 0 9m
|
||||
pod/prometheus-operator-5c8774cdd8-7c5qm 1/1 Running 0 9m
|
||||
pod/prometheus-prometheus-0 2/2 Running 0 9m
|
||||
pod/redis-master-9b6b86c46-b7ggn 1/1 Running 0 9m
|
||||
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
service/kubernetes ClusterIP 10.59.240.1 <none> 443/TCP 19m
|
||||
service/om-backend-metrics ClusterIP 10.59.254.43 <none> 29555/TCP 9m
|
||||
service/om-backendapi ClusterIP 10.59.240.211 <none> 50505/TCP 9m
|
||||
service/om-frontend-metrics ClusterIP 10.59.246.228 <none> 19555/TCP 9m
|
||||
service/om-frontendapi ClusterIP 10.59.250.59 <none> 50504/TCP 9m
|
||||
service/om-mmforc-metrics ClusterIP 10.59.240.59 <none> 39555/TCP 9m
|
||||
service/om-mmlogicapi ClusterIP 10.59.248.3 <none> 50503/TCP 9m
|
||||
service/prometheus NodePort 10.59.252.212 <none> 9090:30900/TCP 9m
|
||||
service/prometheus-operated ClusterIP None <none> 9090/TCP 9m
|
||||
service/redis-sentinel ClusterIP 10.59.249.197 <none> 6379/TCP 9m
|
||||
|
||||
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
|
||||
deployment.extensions/om-backendapi 1 1 1 1 9m
|
||||
deployment.extensions/om-frontendapi 1 1 1 1 9m
|
||||
deployment.extensions/om-mmforc 1 1 1 1 9m
|
||||
deployment.extensions/om-mmlogicapi 1 1 1 1 9m
|
||||
deployment.extensions/prometheus-operator 1 1 1 1 9m
|
||||
deployment.extensions/redis-master 1 1 1 1 9m
|
||||
|
||||
NAME DESIRED CURRENT READY AGE
|
||||
replicaset.extensions/om-backendapi-84bc9d8fff 1 1 1 9m
|
||||
replicaset.extensions/om-frontendapi-55d5bb7946 1 1 1 9m
|
||||
replicaset.extensions/om-mmforc-85bfd7f4f6 1 1 1 9m
|
||||
replicaset.extensions/om-mmlogicapi-6488bc7fc6 1 1 1 9m
|
||||
replicaset.extensions/prometheus-operator-5c8774cdd8 1 1 1 9m
|
||||
replicaset.extensions/redis-master-9b6b86c46 1 1 1 9m
|
||||
|
||||
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
|
||||
deployment.apps/om-backendapi 1 1 1 1 9m
|
||||
deployment.apps/om-frontendapi 1 1 1 1 9m
|
||||
deployment.apps/om-mmforc 1 1 1 1 9m
|
||||
deployment.apps/om-mmlogicapi 1 1 1 1 9m
|
||||
deployment.apps/prometheus-operator 1 1 1 1 9m
|
||||
deployment.apps/redis-master 1 1 1 1 9m
|
||||
|
||||
NAME DESIRED CURRENT READY AGE
|
||||
replicaset.apps/om-backendapi-84bc9d8fff 1 1 1 9m
|
||||
replicaset.apps/om-frontendapi-55d5bb7946 1 1 1 9m
|
||||
replicaset.apps/om-mmforc-85bfd7f4f6 1 1 1 9m
|
||||
replicaset.apps/om-mmlogicapi-6488bc7fc6 1 1 1 9m
|
||||
replicaset.apps/prometheus-operator-5c8774cdd8 1 1 1 9m
|
||||
replicaset.apps/redis-master-9b6b86c46 1 1 1 9m
|
||||
|
||||
NAME DESIRED CURRENT AGE
|
||||
statefulset.apps/prometheus-prometheus 1 1 9m
|
||||
```
|
||||
|
||||
### End-to-End testing
|
||||
|
||||
**Note** The programs provided below are just bare-bones manual testing programs with no automation and no claim of code coverage. This sparseness of this part of the documentation is because we expect to discard all of these tools and write a fully automated end-to-end test suite and a collection of load testing tools, with extensive stats output and tracing capabilities before 1.0 release. Tracing has to be integrated first, which will be in an upcoming release.
|
||||
**Note**: The programs provided below are just bare-bones manual testing programs with no automation and no claim of code coverage. This sparseness of this part of the documentation is because we expect to discard all of these tools and write a fully automated end-to-end test suite and a collection of load testing tools, with extensive stats output and tracing capabilities before 1.0 release. Tracing has to be integrated first, which will be in an upcoming release.
|
||||
|
||||
In the end: *caveat emptor*. These tools all work and are quite small, and as such are fairly easy for developers to understand by looking at the code and logging output. They are provided as-is just as a reference point of how to begin experimenting with Open Match integrations.
|
||||
In the end: *caveat emptor*. These tools all work and are quite small, and as such are fairly easy for developers to understand by looking at the code and logging output. They are provided as-is just as a reference point of how to begin experimenting with Open Match integrations.
|
||||
|
||||
* `examples/frontendclient` is a fake client for the Frontend API. It pretends to be a real game client connecting to Open Match and requests a game, then dumps out the connection string it receives. Note that it doesn't actually test the return path by looking for arbitrary results from your matchmaking function; it pauses and tells you the name of a key to set a connection string in directly using a redis-cli client.
|
||||
* `examples/backendclient` is a fake client for the Backend API. It pretends to be a dedicated game server backend connecting to openmatch and sending in a match profile to fill. Once it receives a match object with a roster, it will also issue a call to assign the player IDs, and gives an example connection string. If it never seems to get a match, make sure you're adding players to the pool using the other two tools.
|
||||
* `test/cmd/client` is a (VERY) basic client load simulation tool. It does **not** test the Frontend API - in fact, it ignores it and writes players directly to state storage on its own. It doesn't do anything but loop endlessly, writing players into state storage so you can test your backend integration, and run your custom MMFs and Evaluators (which are only triggered when there are players in the pool).
|
||||
* `examples/frontendclient` is a fake client for the Frontend API. It pretends to be a real game client connecting to Open Match and requests a game, then dumps out the connection string it receives. Note that it doesn't actually test the return path by looking for arbitrary results from your matchmaking function; it pauses and tells you the name of a key to set a connection string in directly using a redis-cli client. **Note**: If you're using the rest of these test programs, you're probably using the Backend Client below. The default profiles that sends to the backend look for way more than one player, so if you want to see meaningful results from running this Frontend Client, you're going to need to generate a bunch of fake players using the client load simulation tool at the same time. Otherwise, expect to wait until it times out as your matchmaker never has enough players to make a successful match.
|
||||
* `examples/backendclient` is a fake client for the Backend API. It pretends to be a dedicated game server backend connecting to openmatch and sending in a match profile to fill. Once it receives a match object with a roster, it will also issue a call to assign the player IDs, and gives an example connection string. If it never seems to get a match, make sure you're adding players to the pool using the other two tools. Note: building this image requires that you first build the 'base' dev image (look for `cloudbuild_base.yaml` and `Dockerfile.base` in the root directory) and then update the first step to point to that image in your registry. This will be simplified in a future release. **Note**: If you run this by itself, expect it to wait about 30 seconds, then return a result of 'insufficient players' and exit - this is working as intended. Use the client load simulation tool below to add players to the pool or you'll never be able to make a successful match.
|
||||
* `test/cmd/client` is a (VERY) basic client load simulation tool. It does **not** test the Frontend API - in fact, it ignores it and writes players directly to state storage on its own. It doesn't do anything but loop endlessly, writing players into state storage so you can test your backend integration, and run your custom MMFs and Evaluators (which are only triggered when there are players in the pool).
|
||||
|
||||
### Resources
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
FROM golang:1.10.3 as builder
|
||||
#FROM golang:1.10.3 as builder
|
||||
FROM gcr.io/matchmaker-dev-201405/openmatch-devbase as builder
|
||||
WORKDIR /go/src/github.com/GoogleCloudPlatform/open-match/examples/backendclient
|
||||
COPY ./ ./
|
||||
RUN go get -d -v
|
||||
|
@ -1,8 +1,11 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [ 'pull', 'gcr.io/$PROJECT_ID/openmatch-devbase' ]
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'--tag=gcr.io/$PROJECT_ID/openmatch-backendclient:dev',
|
||||
'--cache-from=gcr.io/$PROJECT_ID/openmatch-devbase:latest',
|
||||
'.'
|
||||
]
|
||||
images: ['gcr.io/$PROJECT_ID/openmatch-backendclient:dev']
|
||||
|
@ -25,14 +25,14 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
backend "github.com/GoogleCloudPlatform/open-match/examples/backendclient/proto"
|
||||
backend "github.com/GoogleCloudPlatform/open-match/internal/pb"
|
||||
"github.com/tidwall/gjson"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
@ -70,6 +70,15 @@ func main() {
|
||||
}
|
||||
|
||||
jsonProfile := buffer.String()
|
||||
pbProfile := &backend.MatchObject{}
|
||||
/*
|
||||
err = jsonpb.UnmarshalString(jsonProfile, pbProfile)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
*/
|
||||
pbProfile.Properties = jsonProfile
|
||||
|
||||
log.Println("Requesting matches that fit profile:")
|
||||
ppJSON(jsonProfile)
|
||||
//jsonProfile := bytesToString(jsonData)
|
||||
@ -84,24 +93,26 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect: %s", err.Error())
|
||||
}
|
||||
client := backend.NewAPIClient(conn)
|
||||
client := backend.NewBackendClient(conn)
|
||||
log.Println("API client connected to", ip[0]+":50505")
|
||||
|
||||
// Test CreateMatch
|
||||
p := &backend.Profile{
|
||||
Id: "test-dm-usc1f",
|
||||
// Make a stub debug hostname from the current time
|
||||
Properties: jsonProfile,
|
||||
profileName := "test-dm-usc1f"
|
||||
_ = profileName
|
||||
if gjson.Get(jsonProfile, "name").Exists() {
|
||||
profileName = gjson.Get(jsonProfile, "name").String()
|
||||
}
|
||||
|
||||
//
|
||||
//log.Printf("Looking for matches for profile for the next 5 seconds:")
|
||||
pbProfile.Id = profileName
|
||||
pbProfile.Properties = jsonProfile
|
||||
|
||||
log.Printf("Establishing HTTPv2 stream...")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
//match, err := client.CreateMatch(ctx, pbProfile)
|
||||
|
||||
for {
|
||||
log.Println("Attempting to send ListMatches call")
|
||||
stream, err := client.ListMatches(ctx, p)
|
||||
stream, err := client.ListMatches(ctx, pbProfile)
|
||||
if err != nil {
|
||||
log.Fatalf("Attempting to open stream for ListMatches(_) = _, %v", err)
|
||||
}
|
||||
@ -117,8 +128,6 @@ func main() {
|
||||
break
|
||||
}
|
||||
|
||||
log.Println("Received match:")
|
||||
ppJSON(match.Properties)
|
||||
if match.Properties == "{error: insufficient_players}" {
|
||||
log.Println("Waiting for a larger player pool...")
|
||||
break
|
||||
@ -126,43 +135,46 @@ func main() {
|
||||
|
||||
// Validate JSON before trying to parse it
|
||||
if !gjson.Valid(string(match.Properties)) {
|
||||
log.Fatal(errors.New("invalid json"))
|
||||
log.Println(errors.New("invalid json"))
|
||||
}
|
||||
log.Println("Received match:")
|
||||
ppJSON(match.Properties)
|
||||
fmt.Println(match)
|
||||
|
||||
// Get players from the json properties.roster field
|
||||
log.Println("Gathering roster from received match...")
|
||||
players := make([]string, 0)
|
||||
result := gjson.Get(match.Properties, "properties.roster")
|
||||
result.ForEach(func(teamName, teamRoster gjson.Result) bool {
|
||||
teamRoster.ForEach(func(_, player gjson.Result) bool {
|
||||
players = append(players, player.String())
|
||||
/*
|
||||
// Get players from the json properties.roster field
|
||||
log.Println("Gathering roster from received match...")
|
||||
players := make([]string, 0)
|
||||
result := gjson.Get(match.Properties, "properties.roster")
|
||||
result.ForEach(func(teamName, teamRoster gjson.Result) bool {
|
||||
teamRoster.ForEach(func(_, player gjson.Result) bool {
|
||||
players = append(players, player.String())
|
||||
return true // keep iterating
|
||||
})
|
||||
return true // keep iterating
|
||||
})
|
||||
return true // keep iterating
|
||||
})
|
||||
//log.Printf("players = %+v\n", players)
|
||||
//log.Printf("players = %+v\n", players)
|
||||
|
||||
// Assign players in this match to our server
|
||||
log.Println("Assigning players to DGS at example.com:12345")
|
||||
// Assign players in this match to our server
|
||||
log.Println("Assigning players to DGS at example.com:12345")
|
||||
|
||||
playerstr := strings.Join(players, " ")
|
||||
playerstr := strings.Join(players, " ")
|
||||
|
||||
roster := &backend.Roster{PlayerIds: playerstr}
|
||||
ci := &backend.ConnectionInfo{ConnectionString: "example.com:12345"}
|
||||
roster := &backend.Roster{PlayerIds: playerstr}
|
||||
ci := &backend.ConnectionInfo{ConnectionString: "example.com:12345"}
|
||||
|
||||
assign := &backend.Assignments{Roster: roster, ConnectionInfo: ci}
|
||||
_, err = client.CreateAssignments(context.Background(), assign)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assign := &backend.Assignments{Roster: roster, ConnectionInfo: ci}
|
||||
_, err = client.CreateAssignments(context.Background(), assign)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
log.Println("deleting assignments")
|
||||
playerstr = strings.Join(players[0:len(players)/2], " ")
|
||||
roster.PlayerIds = playerstr
|
||||
_, err = client.DeleteAssignments(context.Background(), roster)
|
||||
*/
|
||||
//log.Println("deleting assignments")
|
||||
//playerstr = strings.Join(players[0:len(players)/2], " ")
|
||||
//roster.PlayerIds = playerstr
|
||||
//_, err = client.DeleteAssignments(context.Background(), roster)
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"name":"testprofile",
|
||||
"properties":{
|
||||
"playerPool":{
|
||||
"region.europe-west1":"0-150",
|
||||
"mmr.rating":"950-1650",
|
||||
"mode.ctf":"0-9999999999"
|
||||
},
|
||||
"roster":{
|
||||
"blue":50,
|
||||
"red": 50
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"name":"testprofile",
|
||||
"properties":{
|
||||
"playerPool":{
|
||||
"region.europe-west1":"0-150",
|
||||
"mmr.rating":"950-1650",
|
||||
"mode.ctf":"0-9999999999"
|
||||
},
|
||||
"roster":{
|
||||
"blue":4,
|
||||
"red":4
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,52 @@
|
||||
{
|
||||
"name":"testprofile",
|
||||
"properties":{
|
||||
"playerPool":{
|
||||
"region.europe-west1":"0-150",
|
||||
"mmr.rating":"950-1650",
|
||||
"mode.ctf":"0-9999999999"
|
||||
},
|
||||
"roster":{
|
||||
"blue":4,
|
||||
"red":4
|
||||
}
|
||||
"imagename":"gcr.io/matchmaker-dev-201405/openmatch-mmf:py3",
|
||||
"name":"testprofilev1",
|
||||
"id":"testprofile",
|
||||
"properties":{
|
||||
"pools": [
|
||||
{
|
||||
"name": "defaultPool",
|
||||
"filters": [
|
||||
{ "name": "europeWest1ElapsedUnder150", "attribute": "region.europe-west1", "maxv": "150" },
|
||||
{ "name": "silverRanking", "attribute": "mmr.rating", "maxv": "1250", "minv": "950" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "supportPool",
|
||||
"filters": [
|
||||
{ "name": "europeWest1ElapsedUnder150", "attribute": "region.europe-west1", "maxv": "150" },
|
||||
{ "name": "silverRanking", "attribute": "mmr.rating", "maxv": "1250", "minv": "950" },
|
||||
{ "name": "supportRole", "attribute": "role.support", "maxv": "2147483647" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"rosters": [
|
||||
{
|
||||
"name": "red",
|
||||
"players": [
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "blu",
|
||||
"players": [
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" },
|
||||
{ "pool": "defaultPool" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,483 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: backend.proto
|
||||
|
||||
/*
|
||||
Package backend is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
backend.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Profile
|
||||
MatchObject
|
||||
Result
|
||||
Roster
|
||||
ConnectionInfo
|
||||
Assignments
|
||||
*/
|
||||
package backend
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// Data structure for a profile to pass to the matchmaking function.
|
||||
type Profile struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
Properties string `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Profile) Reset() { *m = Profile{} }
|
||||
func (m *Profile) String() string { return proto.CompactTextString(m) }
|
||||
func (*Profile) ProtoMessage() {}
|
||||
func (*Profile) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *Profile) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Profile) GetProperties() string {
|
||||
if m != nil {
|
||||
return m.Properties
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Data structure for all the properties of a match.
|
||||
type MatchObject struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
Properties string `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
func (m *MatchObject) Reset() { *m = MatchObject{} }
|
||||
func (m *MatchObject) String() string { return proto.CompactTextString(m) }
|
||||
func (*MatchObject) ProtoMessage() {}
|
||||
func (*MatchObject) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func (m *MatchObject) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *MatchObject) GetProperties() string {
|
||||
if m != nil {
|
||||
return m.Properties
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Simple message to return success/failure and error status.
|
||||
type Result struct {
|
||||
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
|
||||
Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Result) Reset() { *m = Result{} }
|
||||
func (m *Result) String() string { return proto.CompactTextString(m) }
|
||||
func (*Result) ProtoMessage() {}
|
||||
func (*Result) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
||||
|
||||
func (m *Result) GetSuccess() bool {
|
||||
if m != nil {
|
||||
return m.Success
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Result) GetError() string {
|
||||
if m != nil {
|
||||
return m.Error
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Data structure to hold a list of players in a match.
|
||||
type Roster struct {
|
||||
PlayerIds string `protobuf:"bytes,1,opt,name=player_ids,json=playerIds" json:"player_ids,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Roster) Reset() { *m = Roster{} }
|
||||
func (m *Roster) String() string { return proto.CompactTextString(m) }
|
||||
func (*Roster) ProtoMessage() {}
|
||||
func (*Roster) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
|
||||
|
||||
func (m *Roster) GetPlayerIds() string {
|
||||
if m != nil {
|
||||
return m.PlayerIds
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Simple message used to pass the connection string for the DGS to the player.
|
||||
type ConnectionInfo struct {
|
||||
ConnectionString string `protobuf:"bytes,1,opt,name=connection_string,json=connectionString" json:"connection_string,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ConnectionInfo) Reset() { *m = ConnectionInfo{} }
|
||||
func (m *ConnectionInfo) String() string { return proto.CompactTextString(m) }
|
||||
func (*ConnectionInfo) ProtoMessage() {}
|
||||
func (*ConnectionInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
|
||||
|
||||
func (m *ConnectionInfo) GetConnectionString() string {
|
||||
if m != nil {
|
||||
return m.ConnectionString
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Assignments struct {
|
||||
Roster *Roster `protobuf:"bytes,1,opt,name=roster" json:"roster,omitempty"`
|
||||
ConnectionInfo *ConnectionInfo `protobuf:"bytes,2,opt,name=connection_info,json=connectionInfo" json:"connection_info,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Assignments) Reset() { *m = Assignments{} }
|
||||
func (m *Assignments) String() string { return proto.CompactTextString(m) }
|
||||
func (*Assignments) ProtoMessage() {}
|
||||
func (*Assignments) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
|
||||
|
||||
func (m *Assignments) GetRoster() *Roster {
|
||||
if m != nil {
|
||||
return m.Roster
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Assignments) GetConnectionInfo() *ConnectionInfo {
|
||||
if m != nil {
|
||||
return m.ConnectionInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Profile)(nil), "Profile")
|
||||
proto.RegisterType((*MatchObject)(nil), "MatchObject")
|
||||
proto.RegisterType((*Result)(nil), "Result")
|
||||
proto.RegisterType((*Roster)(nil), "Roster")
|
||||
proto.RegisterType((*ConnectionInfo)(nil), "ConnectionInfo")
|
||||
proto.RegisterType((*Assignments)(nil), "Assignments")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for API service
|
||||
|
||||
type APIClient interface {
|
||||
// Calls to ask the matchmaker to run a matchmaking function.
|
||||
//
|
||||
// Run MMF once. Return a matchobject that fits this profile.
|
||||
CreateMatch(ctx context.Context, in *Profile, opts ...grpc.CallOption) (*MatchObject, error)
|
||||
// Continually run MMF and stream matchobjects that fit this profile until
|
||||
// client closes the connection.
|
||||
ListMatches(ctx context.Context, in *Profile, opts ...grpc.CallOption) (API_ListMatchesClient, error)
|
||||
// Delete a matchobject from state storage manually. (Matchobjects in state
|
||||
// storage will also automatically expire after a while)
|
||||
DeleteMatch(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*Result, error)
|
||||
// Call that manage communication of DGS connection info to players.
|
||||
//
|
||||
// Write the DGS connection info for the list of players in the
|
||||
// Assignments.roster to state storage, so that info can be read by the game
|
||||
// client(s).
|
||||
CreateAssignments(ctx context.Context, in *Assignments, opts ...grpc.CallOption) (*Result, error)
|
||||
// Remove DGS connection info for the list of players in the Roster from
|
||||
// state storage.
|
||||
DeleteAssignments(ctx context.Context, in *Roster, opts ...grpc.CallOption) (*Result, error)
|
||||
}
|
||||
|
||||
type aPIClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewAPIClient(cc *grpc.ClientConn) APIClient {
|
||||
return &aPIClient{cc}
|
||||
}
|
||||
|
||||
func (c *aPIClient) CreateMatch(ctx context.Context, in *Profile, opts ...grpc.CallOption) (*MatchObject, error) {
|
||||
out := new(MatchObject)
|
||||
err := grpc.Invoke(ctx, "/API/CreateMatch", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *aPIClient) ListMatches(ctx context.Context, in *Profile, opts ...grpc.CallOption) (API_ListMatchesClient, error) {
|
||||
stream, err := grpc.NewClientStream(ctx, &_API_serviceDesc.Streams[0], c.cc, "/API/ListMatches", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &aPIListMatchesClient{stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type API_ListMatchesClient interface {
|
||||
Recv() (*MatchObject, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type aPIListMatchesClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *aPIListMatchesClient) Recv() (*MatchObject, error) {
|
||||
m := new(MatchObject)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *aPIClient) DeleteMatch(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/API/DeleteMatch", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *aPIClient) CreateAssignments(ctx context.Context, in *Assignments, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/API/CreateAssignments", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *aPIClient) DeleteAssignments(ctx context.Context, in *Roster, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/API/DeleteAssignments", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for API service
|
||||
|
||||
type APIServer interface {
|
||||
// Calls to ask the matchmaker to run a matchmaking function.
|
||||
//
|
||||
// Run MMF once. Return a matchobject that fits this profile.
|
||||
CreateMatch(context.Context, *Profile) (*MatchObject, error)
|
||||
// Continually run MMF and stream matchobjects that fit this profile until
|
||||
// client closes the connection.
|
||||
ListMatches(*Profile, API_ListMatchesServer) error
|
||||
// Delete a matchobject from state storage manually. (Matchobjects in state
|
||||
// storage will also automatically expire after a while)
|
||||
DeleteMatch(context.Context, *MatchObject) (*Result, error)
|
||||
// Call that manage communication of DGS connection info to players.
|
||||
//
|
||||
// Write the DGS connection info for the list of players in the
|
||||
// Assignments.roster to state storage, so that info can be read by the game
|
||||
// client(s).
|
||||
CreateAssignments(context.Context, *Assignments) (*Result, error)
|
||||
// Remove DGS connection info for the list of players in the Roster from
|
||||
// state storage.
|
||||
DeleteAssignments(context.Context, *Roster) (*Result, error)
|
||||
}
|
||||
|
||||
func RegisterAPIServer(s *grpc.Server, srv APIServer) {
|
||||
s.RegisterService(&_API_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _API_CreateMatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Profile)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(APIServer).CreateMatch(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/API/CreateMatch",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(APIServer).CreateMatch(ctx, req.(*Profile))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _API_ListMatches_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(Profile)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(APIServer).ListMatches(m, &aPIListMatchesServer{stream})
|
||||
}
|
||||
|
||||
type API_ListMatchesServer interface {
|
||||
Send(*MatchObject) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type aPIListMatchesServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *aPIListMatchesServer) Send(m *MatchObject) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _API_DeleteMatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MatchObject)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(APIServer).DeleteMatch(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/API/DeleteMatch",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(APIServer).DeleteMatch(ctx, req.(*MatchObject))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _API_CreateAssignments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Assignments)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(APIServer).CreateAssignments(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/API/CreateAssignments",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(APIServer).CreateAssignments(ctx, req.(*Assignments))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _API_DeleteAssignments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Roster)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(APIServer).DeleteAssignments(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/API/DeleteAssignments",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(APIServer).DeleteAssignments(ctx, req.(*Roster))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _API_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "API",
|
||||
HandlerType: (*APIServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "CreateMatch",
|
||||
Handler: _API_CreateMatch_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteMatch",
|
||||
Handler: _API_DeleteMatch_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CreateAssignments",
|
||||
Handler: _API_CreateAssignments_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteAssignments",
|
||||
Handler: _API_DeleteAssignments_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "ListMatches",
|
||||
Handler: _API_ListMatches_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "backend.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("backend.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 344 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xcf, 0x4e, 0xc2, 0x40,
|
||||
0x10, 0xc6, 0x29, 0xc6, 0x16, 0x66, 0x11, 0x64, 0xe3, 0x81, 0x90, 0xf8, 0x27, 0x3d, 0x88, 0x46,
|
||||
0xb3, 0x31, 0x78, 0xc1, 0x83, 0x07, 0x82, 0x17, 0x12, 0x8d, 0xa4, 0x3e, 0x00, 0x29, 0xdb, 0x01,
|
||||
0x56, 0xeb, 0x6e, 0xb3, 0xbb, 0x1c, 0x7c, 0x53, 0x1f, 0xc7, 0xb8, 0x2d, 0xba, 0x1c, 0x3c, 0x78,
|
||||
0x9c, 0x5f, 0xbf, 0x6f, 0xe6, 0xeb, 0xcc, 0xc2, 0xc1, 0x22, 0xe5, 0x6f, 0x28, 0x33, 0x56, 0x68,
|
||||
0x65, 0x55, 0x7c, 0x07, 0xd1, 0x4c, 0xab, 0xa5, 0xc8, 0x91, 0xb6, 0xa1, 0x2e, 0xb2, 0x5e, 0x70,
|
||||
0x16, 0x5c, 0x34, 0x93, 0xba, 0xc8, 0xe8, 0x09, 0x40, 0xa1, 0x55, 0x81, 0xda, 0x0a, 0x34, 0xbd,
|
||||
0xba, 0xe3, 0x1e, 0x89, 0xef, 0x81, 0x3c, 0xa5, 0x96, 0xaf, 0x9f, 0x17, 0xaf, 0xc8, 0xed, 0xbf,
|
||||
0xed, 0x23, 0x08, 0x13, 0x34, 0x9b, 0xdc, 0xd2, 0x1e, 0x44, 0x66, 0xc3, 0x39, 0x1a, 0xe3, 0xec,
|
||||
0x8d, 0x64, 0x5b, 0xd2, 0x23, 0xd8, 0x47, 0xad, 0x95, 0xae, 0xec, 0x65, 0x11, 0x0f, 0x20, 0x4c,
|
||||
0x94, 0xb1, 0xa8, 0xe9, 0x31, 0x40, 0x91, 0xa7, 0x1f, 0xa8, 0xe7, 0x22, 0x33, 0xd5, 0xec, 0x66,
|
||||
0x49, 0xa6, 0xd9, 0x77, 0xc2, 0xf6, 0x44, 0x49, 0x89, 0xdc, 0x0a, 0x25, 0xa7, 0x72, 0xa9, 0xe8,
|
||||
0x15, 0x74, 0xf9, 0x0f, 0x99, 0x1b, 0xab, 0x85, 0x5c, 0x55, 0xbe, 0xc3, 0xdf, 0x0f, 0x2f, 0x8e,
|
||||
0xc7, 0x6b, 0x20, 0x63, 0x63, 0xc4, 0x4a, 0xbe, 0xa3, 0xb4, 0x86, 0x9e, 0x42, 0xa8, 0xdd, 0x58,
|
||||
0x67, 0x20, 0xc3, 0x88, 0x95, 0x29, 0x92, 0x0a, 0xd3, 0x11, 0x74, 0xbc, 0xe6, 0x42, 0x2e, 0x95,
|
||||
0xcb, 0x4d, 0x86, 0x1d, 0xb6, 0x1b, 0x23, 0x69, 0xf3, 0x9d, 0x7a, 0xf8, 0x19, 0xc0, 0xde, 0x78,
|
||||
0x36, 0xa5, 0x03, 0x20, 0x13, 0x8d, 0xa9, 0x45, 0xb7, 0x58, 0xda, 0x60, 0xd5, 0x6d, 0xfa, 0x2d,
|
||||
0xe6, 0xad, 0x3a, 0xae, 0xd1, 0x4b, 0x20, 0x8f, 0xc2, 0x58, 0x07, 0xd1, 0xfc, 0x2d, 0xbc, 0x09,
|
||||
0xe8, 0x39, 0x90, 0x07, 0xcc, 0x71, 0xdb, 0x73, 0x47, 0xd0, 0x8f, 0x58, 0x79, 0x83, 0xb8, 0x46,
|
||||
0xaf, 0xa1, 0x5b, 0xce, 0xf6, 0xff, 0xb9, 0xc5, 0xbc, 0xca, 0x57, 0x0f, 0xa0, 0x5b, 0x76, 0xf5,
|
||||
0xd5, 0xdb, 0x8d, 0x78, 0xc2, 0x45, 0xe8, 0xde, 0xd9, 0xed, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xf2, 0x23, 0x14, 0x36, 0x78, 0x02, 0x00, 0x00,
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
// package backend should be a copy of the compiled gRPC protobuf file used by the backend API.
|
||||
package backend
|
@ -25,12 +25,15 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
om_messages "github.com/GoogleCloudPlatform/open-match/internal/pb"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/redispb"
|
||||
"github.com/gobs/pretty"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/spf13/viper"
|
||||
@ -79,7 +82,7 @@ func main() {
|
||||
|
||||
start := time.Now()
|
||||
|
||||
proposedMatchIds, overloadedPlayers, overloadedMatches, approvedMatches, err := stub(cfg, redisConn)
|
||||
proposedMatchIds, overloadedPlayers, overloadedMatches, approvedMatches, err := stub(cfg, &pool)
|
||||
overloadedPlayerList, overloadedMatchList, approvedMatchList := generateLists(overloadedPlayers, overloadedMatches, approvedMatches)
|
||||
|
||||
fmt.Println("overloadedPlayers")
|
||||
@ -99,13 +102,16 @@ func main() {
|
||||
// The match object was already written by the MMF, just change the
|
||||
// name to what the Backend API (apisrv.go) is looking for.
|
||||
proposedID := proposedMatchIds[proposalIndex]
|
||||
// Incoming proposal keys look like this:
|
||||
// proposal.1542600048.80e43fa085844eebbf53fc736150ef96.testprofile
|
||||
// format:
|
||||
// "proposal".timestamp.unique_matchobject_id.profile_name
|
||||
values := strings.Split(proposedID, ".")
|
||||
timestamp, moID, proID := values[0], values[1], values[2]
|
||||
proposalID := "proposal." + timestamp + "." + moID + "." + proID
|
||||
moID, proID := values[2], values[3]
|
||||
backendID := moID + "." + proID
|
||||
fmt.Printf("approving proposal #%+v:%+v\n", proposalIndex, moID)
|
||||
fmt.Println("RENAME", proposalID, backendID)
|
||||
_, err = redisConn.Do("RENAME", proposalID, backendID)
|
||||
fmt.Println("RENAME", proposedID, backendID)
|
||||
_, err = redisConn.Do("RENAME", proposedID, backendID)
|
||||
if err != nil {
|
||||
// RENAME only fails if the source key doesn't exist
|
||||
fmt.Printf("err = %+v\n", err)
|
||||
@ -175,29 +181,26 @@ func readConfig(filename string, defaults map[string]interface{}) (*viper.Viper,
|
||||
return v, err
|
||||
}
|
||||
|
||||
func stub(cfg *viper.Viper, redisConn redis.Conn) ([]string, map[string][]int, map[int][]int, map[int]bool, error) {
|
||||
func stub(cfg *viper.Viper, pool *redis.Pool) ([]string, map[string][]int, map[int][]int, map[int]bool, error) {
|
||||
//Init Logger
|
||||
lgr := log.New(os.Stdout, "MMFEvalStub: ", log.LstdFlags)
|
||||
lgr.Println("Initializing example MMF proposal evaluator")
|
||||
|
||||
// Get redis conneciton
|
||||
redisConn := pool.Get()
|
||||
defer redisConn.Close()
|
||||
|
||||
// Put some config vars into other vars for readability
|
||||
proposalq := cfg.GetString("queues.proposals.name")
|
||||
|
||||
lgr.Println("SCARD", proposalq)
|
||||
numProposals, err := redis.Int(redisConn.Do("SCARD", proposalq))
|
||||
lgr.Println("SPOP", proposalq, numProposals)
|
||||
propKeys, err := redis.Strings(redisConn.Do("SPOP", proposalq, numProposals))
|
||||
proposals, err := redis.Strings(redisConn.Do("SPOP", proposalq, numProposals))
|
||||
if err != nil {
|
||||
lgr.Println(err)
|
||||
}
|
||||
fmt.Printf("propKeys = %+v\n", propKeys)
|
||||
|
||||
// Convert []string to []interface{} so it can be passed as variadic input
|
||||
// https://golang.org/doc/faq#convert_slice_of_interface
|
||||
rosterKeys := propToRoster(propKeys)
|
||||
lgr.Printf("MGET %+v", rosterKeys)
|
||||
rosters, err := redis.Strings(redisConn.Do("MGET", rosterKeys...))
|
||||
pretty.PrettyPrint(rosters)
|
||||
fmt.Printf("proposals = %+v\n", proposals)
|
||||
|
||||
// This is a far cry from effecient but we expect a pretty small set of players under consideration
|
||||
// at any given time
|
||||
@ -206,11 +209,16 @@ func stub(cfg *viper.Viper, redisConn redis.Conn) ([]string, map[string][]int, m
|
||||
overloadedMatches := make(map[int][]int)
|
||||
approvedMatches := make(map[int]bool)
|
||||
allPlayers := make(map[string]int)
|
||||
for index := range rosters {
|
||||
approvedMatches[index] = true
|
||||
}
|
||||
for index, playerList := range rosters {
|
||||
for _, pID := range strings.Split(playerList, " ") {
|
||||
|
||||
// Loop through each proposal, and look for 'overloaded' players (players in multiple proposals)
|
||||
for index, propKey := range proposals {
|
||||
approvedMatches[index] = true // This proposal is approved until proven otherwise
|
||||
playerList, err := getProposedPlayers(pool, propKey)
|
||||
if err != nil {
|
||||
lgr.Println(err)
|
||||
}
|
||||
|
||||
for _, pID := range playerList {
|
||||
if allPlayers[pID] != 0 {
|
||||
// Seen this player at least once before; gather the indicies of all the match
|
||||
// proposals with this player
|
||||
@ -235,10 +243,33 @@ func stub(cfg *viper.Viper, redisConn redis.Conn) ([]string, map[string][]int, m
|
||||
}
|
||||
}
|
||||
}
|
||||
return propKeys, overloadedPlayers, overloadedMatches, approvedMatches, err
|
||||
return proposals, overloadedPlayers, overloadedMatches, approvedMatches, err
|
||||
}
|
||||
|
||||
// getProposedPlayers is a function that may be moved to an API call in the future.
|
||||
func getProposedPlayers(pool *redis.Pool, propKey string) ([]string, error) {
|
||||
|
||||
// Get the proposal match object from redis
|
||||
mo := &om_messages.MatchObject{Id: propKey}
|
||||
err := redispb.UnmarshalFromRedis(context.Background(), pool, mo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Loop through all rosters, appending players IDs to a list.
|
||||
playerList := make([]string, 0)
|
||||
for _, r := range mo.Rosters {
|
||||
for _, p := range r.Players {
|
||||
playerList = append(playerList, p.Id)
|
||||
}
|
||||
}
|
||||
|
||||
return playerList, err
|
||||
}
|
||||
|
||||
func propToRoster(in []string) []interface{} {
|
||||
// Convert []string to []interface{} so it can be passed as variadic input
|
||||
// https://golang.org/doc/faq#convert_slice_of_interface
|
||||
out := make([]interface{}, len(in))
|
||||
for i, v := range in {
|
||||
values := strings.Split(v, ".")
|
||||
|
355
examples/functions/golang/manual-simple/main.go
Normal file
355
examples/functions/golang/manual-simple/main.go
Normal file
@ -0,0 +1,355 @@
|
||||
/*
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/open-match/config"
|
||||
messages "github.com/GoogleCloudPlatform/open-match/internal/pb"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/set"
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/ignorelist"
|
||||
"github.com/gogo/protobuf/jsonpb"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
/*
|
||||
Here are the things a MMF needs to do:
|
||||
|
||||
*Read/write from the Open Match state storage — Open Match ships with Redis as
|
||||
the default state storage.
|
||||
*Be packaged in a (Linux) Docker container.
|
||||
*Read a profile you wrote to state storage using the Backend API.
|
||||
*Select from the player data you wrote to state storage using the Frontend API.
|
||||
*Run your custom logic to try to find a match.
|
||||
*Write the match object it creates to state storage at a specified key.
|
||||
*Remove the players it selected from consideration by other MMFs.
|
||||
*Notify the MMForc of completion.
|
||||
*(Optional & NYI, but recommended) Export stats for metrics collection.
|
||||
*/
|
||||
func main() {
|
||||
// Read config file.
|
||||
cfg := viper.New()
|
||||
cfg, err := config.Read()
|
||||
|
||||
// As per https://www.iana.org/assignments/uri-schemes/prov/redis
|
||||
// redis://user:secret@localhost:6379/0?foo=bar&qux=baz
|
||||
redisURL := "redis://" + os.Getenv("REDIS_SENTINEL_SERVICE_HOST") + ":" + os.Getenv("REDIS_SENTINEL_SERVICE_PORT")
|
||||
fmt.Println("Connecting to Redis at", redisURL)
|
||||
redisConn, err := redis.DialURL(redisURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer redisConn.Close()
|
||||
|
||||
// decrement the number of running MMFs once finished
|
||||
defer func() {
|
||||
fmt.Println("DECR moncurrentMMFs")
|
||||
_, err = redisConn.Do("DECR", "concurrentMMFs")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Environment vars set by the MMForc
|
||||
jobName := os.Getenv("PROFILE")
|
||||
timestamp := os.Getenv("MMF_TIMESTAMP")
|
||||
proposalKey := os.Getenv("MMF_PROPOSAL_ID")
|
||||
profileKey := os.Getenv("MMF_PROFILE_ID")
|
||||
errorKey := os.Getenv("MMF_ERROR_ID")
|
||||
rosterKey := os.Getenv("MMF_ROSTER_ID")
|
||||
_ = jobName
|
||||
_ = timestamp
|
||||
_ = proposalKey
|
||||
_ = profileKey
|
||||
_ = errorKey
|
||||
_ = rosterKey
|
||||
|
||||
fmt.Println("MMF request inserted at ", timestamp)
|
||||
fmt.Println("Looking for profile in key", profileKey)
|
||||
fmt.Println("Placing results in MatchObjectID", proposalKey)
|
||||
|
||||
// Retrieve profile from Redis.
|
||||
// NOTE: This can also be done with a call to the MMLogic API.
|
||||
profile, err := redis.StringMap(redisConn.Do("HGETALL", profileKey))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("=========Profile")
|
||||
p, err := json.MarshalIndent(profile, "", " ")
|
||||
fmt.Println(string(p))
|
||||
|
||||
// select players
|
||||
const numPlayers = 8
|
||||
// ZRANGE is 0-indexed
|
||||
pools := gjson.Get(profile["properties"], cfg.GetString("jsonkeys.pools"))
|
||||
fmt.Println("=========Pools")
|
||||
fmt.Printf("pool.String() = %+v\n", pools.String())
|
||||
|
||||
// Parse all the pools.
|
||||
// NOTE: When using pool definitions like these that are using the
|
||||
// PlayerPool protobuf message data schema, you can avoid all of this by
|
||||
// using the MMLogic API call to automatically parse the pools, run the
|
||||
// filters, and return the results in one gRPC call per pool.
|
||||
//
|
||||
// ex: poolRosters["defaultPool"]["mmr.rating"]=[]string{"abc", "def", "ghi"}
|
||||
poolRosters := make(map[string]map[string][]string)
|
||||
|
||||
// Loop through each pool.
|
||||
pools.ForEach(func(_, pool gjson.Result) bool {
|
||||
pName := gjson.Get(pool.String(), "name").String()
|
||||
pFilters := gjson.Get(pool.String(), "filters")
|
||||
poolRosters[pName] = make(map[string][]string)
|
||||
|
||||
// Loop through each filter for this pool
|
||||
pFilters.ForEach(func(_, filter gjson.Result) bool {
|
||||
// Note: This only works when running only one filter on each attribute!
|
||||
searchKey := gjson.Get(filter.String(), "attribute").String()
|
||||
min := int64(0)
|
||||
max := int64(time.Now().Unix())
|
||||
poolRosters[pName][searchKey] = make([]string, 0)
|
||||
|
||||
// Parse the min and max values.
|
||||
if minv := gjson.Get(filter.String(), "minv"); minv.Bool() {
|
||||
min = int64(minv.Int())
|
||||
}
|
||||
if maxv := gjson.Get(filter.String(), "maxv"); maxv.Bool() {
|
||||
max = int64(maxv.Int())
|
||||
}
|
||||
fmt.Printf("%v: %v: [%v-%v]\n", pName, searchKey, min, max)
|
||||
|
||||
// NOTE: This only pulls the first 50000 matches for a given index!
|
||||
// This is an example, and probably shouldn't be used outside of
|
||||
// testing without some performance tuning based on the size of
|
||||
// your indexes. In prodution, this could be run concurrently on
|
||||
// multiple parts of the index, and combined.
|
||||
// NOTE: It is recommended you also send back some stats about this
|
||||
// query along with your MMF, which can be useful when your backend
|
||||
// API client is deciding which profiles to send. This example does
|
||||
// not return stats, but when using the MMLogic API, this is done
|
||||
// for you.
|
||||
poolRosters[pName][searchKey], err = redis.Strings(
|
||||
redisConn.Do("ZRANGEBYSCORE", searchKey, min, max, "LIMIT", "0", "50000"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return true // keep iterating
|
||||
})
|
||||
|
||||
return true // keep iterating
|
||||
})
|
||||
|
||||
// Get ignored players.
|
||||
combinedIgnoreList := make([]string, 0)
|
||||
// Loop through all ignorelists configured in the config file.
|
||||
for il := range cfg.GetStringMap("ignoreLists") {
|
||||
ilCfg := cfg.Sub(fmt.Sprintf("ignoreLists.%v", il))
|
||||
thisIl, err := ignorelist.Retrieve(redisConn, ilCfg, il)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Join this ignorelist to the others we've retrieved
|
||||
combinedIgnoreList = set.Union(combinedIgnoreList, thisIl)
|
||||
}
|
||||
|
||||
// Cycle through all filters for each pool, and calculate the overlap
|
||||
// (players that match all filters)
|
||||
overlaps := make(map[string][]string)
|
||||
// Loop through pools
|
||||
for pName, p := range poolRosters {
|
||||
fmt.Println(pName)
|
||||
|
||||
// Var init
|
||||
overlaps[pName] = make([]string, 0)
|
||||
first := true // Flag used to initialize the overlap on the first iteration.
|
||||
|
||||
// Loop through rosters that matched each filter
|
||||
for fName, roster := range p {
|
||||
if first {
|
||||
first = false
|
||||
overlaps[pName] = roster
|
||||
}
|
||||
// Calculate overlap
|
||||
overlaps[pName] = set.Intersection(overlaps[pName], roster)
|
||||
|
||||
// Print out for visibility/debugging
|
||||
fmt.Printf(" filtering: %-20v | participants remaining: %-5v\n", fName, len(overlaps[pName]))
|
||||
}
|
||||
|
||||
// Remove players on ignorelists
|
||||
overlaps[pName] = set.Difference(overlaps[pName], combinedIgnoreList)
|
||||
fmt.Printf(" removing: %-21v | participants remaining: %-5v\n", "(ignorelists)", len(overlaps[pName]))
|
||||
}
|
||||
|
||||
// Loop through each roster in the profile and fill in players.
|
||||
rosters := gjson.Get(profile["properties"], cfg.GetString("jsonkeys.rosters"))
|
||||
fmt.Println("=========Rosters")
|
||||
fmt.Printf("rosters.String() = %+v\n", rosters.String())
|
||||
|
||||
// Parse all the rosters in the profile, adding players if we can.
|
||||
// NOTE: This is using roster definitions that follow the Roster protobuf
|
||||
// message data schema.
|
||||
profileRosters := make(map[string][]string)
|
||||
//proposedRosters := make([]string, 0)
|
||||
mo := &messages.MatchObject{}
|
||||
mo.Rosters = make([]*messages.Roster, 0)
|
||||
|
||||
// List of all player IDs on all proposed rosters, used to add players to
|
||||
// the ignore list.
|
||||
// NOTE: when using the MMLogic API, writing your final proposal to state
|
||||
// storage will automatically add players to the ignorelist, so you don't
|
||||
// need to track them separately and add them to the ignore list yourself.
|
||||
playerList := make([]string, 0)
|
||||
|
||||
rosters.ForEach(func(_, roster gjson.Result) bool {
|
||||
rName := gjson.Get(roster.String(), "name").String()
|
||||
fmt.Println(rName)
|
||||
rPlayers := gjson.Get(roster.String(), "players")
|
||||
profileRosters[rName] = make([]string, 0)
|
||||
pbRoster := messages.Roster{Name: rName, Players: []*messages.Player{}}
|
||||
|
||||
rPlayers.ForEach(func(_, player gjson.Result) bool {
|
||||
// TODO: This is where you would put your own custom matchmaking
|
||||
// logic. MMFs have full access to the state storage in Redis, so
|
||||
// you can choose some participants from the pool according to your
|
||||
// favored strategy. You have complete freedom to read the
|
||||
// participant's records from Redis and make decisions accordingly.
|
||||
//
|
||||
// This example just chooses the players in the order they were
|
||||
// returned from state storage.
|
||||
|
||||
//fmt.Printf(" %v\n", player.String()) //DEBUG
|
||||
proposedPlayer := player.String()
|
||||
// Get the name of the pool that the profile wanted this player pulled from.
|
||||
desiredPool := gjson.Get(player.String(), "pool").String()
|
||||
|
||||
if _, ok := overlaps[desiredPool]; ok {
|
||||
// There are players that match all the desired filters.
|
||||
if len(overlaps[desiredPool]) > 0 {
|
||||
// Propose the next player returned from state storage for this
|
||||
// slot in the match rosters.
|
||||
|
||||
// Functionally, a pop from the overlap array into the proposed slot.
|
||||
playerID := ""
|
||||
playerID, overlaps[desiredPool] = overlaps[desiredPool][0], overlaps[desiredPool][1:]
|
||||
|
||||
proposedPlayer, err = sjson.Set(proposedPlayer, "id", playerID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
profileRosters[rName] = append(profileRosters[rName], proposedPlayer)
|
||||
fmt.Printf(" proposing: %v\n", proposedPlayer)
|
||||
|
||||
pbRoster.Players = append(pbRoster.Players, &messages.Player{Id: playerID, Pool: desiredPool})
|
||||
playerList = append(playerList, playerID)
|
||||
|
||||
} else {
|
||||
// Not enough players, exit.
|
||||
fmt.Println("Not enough players in the pool to fill all player slots in requested roster", rName)
|
||||
fmt.Printf("%+v\n", roster.String())
|
||||
fmt.Println("SET", errorKey, `{"error": "insufficient_players"}`)
|
||||
redisConn.Do("SET", errorKey, `{"error": "insufficient_players"}`)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
//proposedRoster, err := sjson.Set(roster.String(), "players", profileRosters[rName])
|
||||
mo.Rosters = append(mo.Rosters, &pbRoster)
|
||||
//fmt.Sprintf("[%v]", strings.Join(profileRosters[rName], ",")))
|
||||
//if err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
//proposedRosters = append(proposedRosters, proposedRoster)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// Write back the match object to state storage so the evaluator can look at it, and update the ignorelist.
|
||||
// NOTE: the MMLogic API CreateProposal automates most of this for you, as
|
||||
// long as you send it properly formatted data (i.e. data that fits the schema of
|
||||
// the protobuf messages)
|
||||
// Add proposed players to the ignorelist so other MMFs won't consider them.
|
||||
fmt.Printf("Adding %v players to ignorelist\n", len(playerList))
|
||||
err = ignorelist.Add(redisConn, "proposed", playerList)
|
||||
if err != nil {
|
||||
fmt.Println("Unable to add proposed players to the ignorelist")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Write the match object that will be sent back to the DGS
|
||||
jmarshaler := jsonpb.Marshaler{}
|
||||
moJSON, err := jmarshaler.MarshalToString(mo)
|
||||
proposedRosters := gjson.Get(moJSON, "rosters")
|
||||
|
||||
fmt.Println("===========Proposal")
|
||||
// Set the properties field.
|
||||
// This is a filthy hack due to the way sjson escapes & quotes values it inserts.
|
||||
// Better in most cases than trying to marshal the JSON into giant multi-dimensional
|
||||
// interface maps only to dump it back out to a string after.
|
||||
// Note: this hack isn't necessary for most users, who just use this same
|
||||
// data directly from the protobuf message 'rosters' field, or write custom
|
||||
// rosters directly to the JSON properties when choosing players. This is here
|
||||
// for backwards compatibility with backends that haven't been updated to take
|
||||
// advantage of the new rosters field in the MatchObject protobuf message introduced
|
||||
// in 0.2.0.
|
||||
profile["properties"], err = sjson.Set(profile["properties"], cfg.GetString("jsonkeys.rosters"), proposedRosters.String())
|
||||
profile["properties"] = strings.Replace(profile["properties"], "\\", "", -1)
|
||||
profile["properties"] = strings.Replace(profile["properties"], "]\"", "]", -1)
|
||||
profile["properties"] = strings.Replace(profile["properties"], "\"[", "[", -1)
|
||||
if err != nil {
|
||||
fmt.Println("problem with sjson")
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Printf("Proposed ID: %v | Properties: %v", proposalKey, profile["properties"])
|
||||
|
||||
// Write the roster that will be sent to the evaluator. This needs to be written to the
|
||||
// "rosters" key of the match object, in the protobuf format for an array of
|
||||
// rosters protobuf messages. You can write this output by hand (not recommended)
|
||||
// or use the MMLogic API call CreateProposal will a filled out MatchObject protobuf message
|
||||
// and let it do the work for you.
|
||||
profile["rosters"] = proposedRosters.String()
|
||||
|
||||
fmt.Println("===========Redis")
|
||||
// Start writing proposed results to Redis.
|
||||
redisConn.Send("MULTI")
|
||||
for key, value := range profile {
|
||||
if key != "id" {
|
||||
fmt.Println("HSET", proposalKey, key, value)
|
||||
redisConn.Send("HSET", proposalKey, key, value)
|
||||
}
|
||||
}
|
||||
|
||||
//Finally, write the propsal key to trigger the evaluation of these results
|
||||
fmt.Println("SADD", cfg.GetString("queues.proposals.name"), proposalKey)
|
||||
redisConn.Send("SADD", cfg.GetString("queues.proposals.name"), proposalKey)
|
||||
_, err = redisConn.Do("EXEC")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
@ -1,230 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleCloudPlatform/open-match/internal/statestorage/redis/playerq"
|
||||
"github.com/gobs/pretty"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
intersect "github.com/juliangruber/go-intersect"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// As per https://www.iana.org/assignments/uri-schemes/prov/redis
|
||||
// redis://user:secret@localhost:6379/0?foo=bar&qux=baz
|
||||
redisURL := "redis://" + os.Getenv("REDIS_SENTINEL_SERVICE_HOST") + ":" + os.Getenv("REDIS_SENTINEL_SERVICE_PORT")
|
||||
|
||||
//Single redis connection
|
||||
fmt.Println("Connecting to Redis at", redisURL)
|
||||
redisConn, err := redis.DialURL(redisURL)
|
||||
check(err, "QUIT")
|
||||
defer redisConn.Close()
|
||||
defer func() {
|
||||
// decrement the number of running MMFs since this one is finished
|
||||
fmt.Println("DECR concurrentMMFs")
|
||||
_, err = redisConn.Do("DECR", "concurrentMMFs")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// PROFILE is passed via the k8s downward API through an env set to jobName.
|
||||
jobName := os.Getenv("PROFILE")
|
||||
fmt.Println("PROFILE from job name", jobName)
|
||||
|
||||
values := strings.Split(jobName, ".")
|
||||
timestamp, moID, proID := values[0], values[1], values[2]
|
||||
_ = timestamp
|
||||
_ = moID
|
||||
profileKey := proID
|
||||
fmt.Println("Looking for profile in key", profileKey)
|
||||
profile, err := redis.String(redisConn.Do("GET", profileKey))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Got profile!")
|
||||
fmt.Println(profile)
|
||||
|
||||
// Redis key under which to store results
|
||||
resultsKey := "proposal." + jobName
|
||||
rosterKey := "roster." + jobName
|
||||
|
||||
// select players
|
||||
const numPlayers = 8
|
||||
// ZRANGE is 0-indexed
|
||||
defaultPool := gjson.Get(profile, "properties.playerPool")
|
||||
fmt.Println("defaultPool")
|
||||
fmt.Printf("defaultPool.String() = %+v\n", defaultPool.String())
|
||||
|
||||
filters := make(map[string]map[string]int)
|
||||
defaultPool.ForEach(func(key, value gjson.Result) bool {
|
||||
// Make a map entry for this filter.
|
||||
searchKey := key.String()
|
||||
filters[searchKey] = map[string]int{"min": 0, "max": 9999999999}
|
||||
|
||||
// Parse the min and max values. JSON format is "min-max"
|
||||
r := strings.Split(value.String(), "-")
|
||||
filters[searchKey]["min"], err = strconv.Atoi(r[0])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
filters[searchKey]["max"], err = strconv.Atoi(r[1])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return true // keep iterating
|
||||
})
|
||||
pretty.PrettyPrint(filters)
|
||||
|
||||
if len(filters) < 1 {
|
||||
fmt.Printf("No filters in the default pool for the profile, %v\n", len(filters))
|
||||
fmt.Println("SET", moID+"."+proID, `{"error": "insufficient_filters"}`)
|
||||
redisConn.Do("SET", moID+"."+proID, `{"error": "insufficient_filters"}`)
|
||||
return
|
||||
}
|
||||
|
||||
//init 2d array
|
||||
stuff := make([][]string, len(filters))
|
||||
for i := range stuff {
|
||||
stuff[i] = make([]string, 0)
|
||||
}
|
||||
|
||||
i := 0
|
||||
for key, value := range filters {
|
||||
// TODO: this needs a lot of time and effort on building sane values for how many IDs to pull at once.
|
||||
// TODO: this should also be run concurrently per index we're filtering on
|
||||
fmt.Printf("key = %+v\n", key)
|
||||
fmt.Printf("value = %+v\n", value)
|
||||
results, err := redis.Strings(redisConn.Do("ZRANGEBYSCORE", key, value["min"], value["max"], "LIMIT", "0", "10000"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Store off these results in the 2d array used to calculate intersections below.
|
||||
stuff[i] = append(stuff[i], results...)
|
||||
i++
|
||||
}
|
||||
|
||||
fmt.Println("overlap")
|
||||
overlap := stuff[0]
|
||||
for i := range stuff {
|
||||
if i > 0 {
|
||||
set := fmt.Sprint(intersect.Hash(overlap, stuff[i]))
|
||||
overlap = strings.Split(set[1:len(set)-1], " ")
|
||||
}
|
||||
}
|
||||
pretty.PrettyPrint(overlap)
|
||||
|
||||
// TODO: rigourous logic to put players in desired groups
|
||||
teamRosters := make(map[string][]string)
|
||||
rosterProfile := gjson.Get(profile, "properties.roster")
|
||||
fmt.Printf("rosterProfile.String() = %+v\n", rosterProfile.String())
|
||||
|
||||
// TODO: get this by parsing the JSON instead of cheating
|
||||
rosterSize := int(gjson.Get(profile, "properties.roster.blue").Int() + gjson.Get(profile, "properties.roster.red").Int())
|
||||
matchRoster := make([]string, 0)
|
||||
|
||||
if len(overlap) < rosterSize {
|
||||
fmt.Printf("Not enough players in the pool to fill %v player slots in requested roster", rosterSize)
|
||||
fmt.Printf("rosterProfile.String() = %+v\n", rosterProfile.String())
|
||||
fmt.Println("SET", moID+"."+proID, `{"error": "insufficient_players"}`)
|
||||
redisConn.Do("SET", moID+"."+proID, `{"error": "insufficient_players"}`)
|
||||
return
|
||||
}
|
||||
rosterProfile.ForEach(func(name, size gjson.Result) bool {
|
||||
teamKey := name.String()
|
||||
teamRosters[teamKey] = make([]string, size.Int())
|
||||
for i := 0; i < int(size.Int()); i++ {
|
||||
var playerID string
|
||||
// Functionally a Pop from the overlap array into playerID
|
||||
playerID, overlap = overlap[0], overlap[1:]
|
||||
teamRosters[teamKey][i] = playerID
|
||||
matchRoster = append(matchRoster, playerID)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
pretty.PrettyPrint(teamRosters)
|
||||
pretty.PrettyPrint(matchRoster)
|
||||
|
||||
profile, err = sjson.Set(profile, "properties.roster", teamRosters)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Write the match object that will be sent back to the DGS
|
||||
fmt.Println("Proposing the following group ", resultsKey)
|
||||
fmt.Println("SET", resultsKey, profile)
|
||||
_, err = redisConn.Do("SET", resultsKey, profile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Write the roster that will be sent to the evaluator
|
||||
fmt.Println("Sending the following roster to the evaluator under key ", rosterKey)
|
||||
fmt.Println("SET", rosterKey, strings.Join(matchRoster, " "))
|
||||
_, err = redisConn.Do("SET", rosterKey, strings.Join(matchRoster, " "))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//TODO: make this auto-correcting if the player doesn't end up in a group.
|
||||
for _, playerID := range matchRoster {
|
||||
fmt.Printf("Attempting to remove player %v from indices\n", playerID)
|
||||
// TODO: make playerq module available to everything
|
||||
err := playerq.Unindex(redisConn, playerID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Finally, write the propsal key to trigger the evaluation of these
|
||||
//results
|
||||
// TODO: read this from a config ala proposalq := cfg.GetString("queues.proposals.name")
|
||||
proposalq := "proposalq"
|
||||
fmt.Println("SADD", proposalq, jobName)
|
||||
_, err = redisConn.Do("SADD", proposalq, jobName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// DEBUG
|
||||
results, err := redis.Strings(redisConn.Do("SMEMBERS", proposalq))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pretty.PrettyPrint(results)
|
||||
}
|
||||
|
||||
func check(err error, action string) {
|
||||
if err != nil {
|
||||
if action == "QUIT" {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
}
|
12
examples/functions/php/mmlogic-simple/composer.json
Normal file
12
examples/functions/php/mmlogic-simple/composer.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"require": {
|
||||
"grpc/grpc": "v1.9.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Api\\": "proto/Api",
|
||||
"Messages\\": "proto/Messages",
|
||||
"GPBMetadata\\": "proto/GPBMetadata"
|
||||
}
|
||||
}
|
||||
}
|
131
examples/functions/php/mmlogic-simple/harness.php
Normal file
131
examples/functions/php/mmlogic-simple/harness.php
Normal file
@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
# Step 1 - Package this in a linux container image.
|
||||
|
||||
require dirname(__FILE__).'/vendor/autoload.php';
|
||||
|
||||
require 'mmf.php';
|
||||
|
||||
function dump_pb_message($msg) {
|
||||
print($msg->serializeToJsonString() . "\n");
|
||||
}
|
||||
|
||||
# Load config file
|
||||
$cfg = json_decode(file_get_contents('matchmaker_config.json'), true);
|
||||
|
||||
# Step 2 - Talk to Redis. This example uses the MM Logic API in OM to read/write to/from redis.
|
||||
# Establish grpc channel and make the API client stub
|
||||
$api_conn_info = sprintf('%s:%s', $cfg['api']['mmlogic']['hostname'], $cfg['api']['mmlogic']['port']);
|
||||
|
||||
$mmlogic_api = new Api\MmLogicClient($api_conn_info, [
|
||||
'credentials' => Grpc\ChannelCredentials::createInsecure(),
|
||||
]);
|
||||
|
||||
# Step 3 - Read the profile written to the Backend API.
|
||||
# Get profile from redis
|
||||
$match_object = new Messages\MatchObject([
|
||||
'id' => getenv('MMF_PROFILE_ID'
|
||||
)]);
|
||||
list($profile_pb, $status) = $mmlogic_api->GetProfile($match_object)->wait();
|
||||
dump_pb_message($profile_pb);
|
||||
|
||||
$profile_dict = json_decode($profile_pb->getProperties(), true);
|
||||
|
||||
# Step 4 - Select the player data from Redis that we want for our matchmaking logic.
|
||||
# Embedded in this profile are JSON representations of the filters for each player pool.
|
||||
# JsonFilterSet() is able to read those directly. No need to marhal that
|
||||
# JSON into the protobuf message format
|
||||
$player_pools = [];
|
||||
foreach ($profile_pb->getPools() as $empty_pool) {
|
||||
$empty_pool_name = $empty_pool->getName();
|
||||
|
||||
# Dict to hold value-sorted field dictionary for easy retreival of players by value
|
||||
$player_pools[$empty_pool_name] = [];
|
||||
printf("Retrieving pool '%s'\n", $empty_pool_name);
|
||||
|
||||
if (!$empty_pool->getStats()) {
|
||||
$empty_pool->setStats(new Messages\Stats());
|
||||
}
|
||||
|
||||
if ($cfg['debug']) {
|
||||
$start = microtime(true);
|
||||
}
|
||||
|
||||
# Pool filter results are streamed in chunks as they can be too large to send
|
||||
# in one grpc message. Loop to get them all.
|
||||
$call = $mmlogic_api->GetPlayerPool($empty_pool);
|
||||
foreach ($call->responses() as $partial_results) {
|
||||
if ($partial_results->getStats()) {
|
||||
$empty_pool->getStats()->setCount($partial_results->getStats()->getCount());
|
||||
$empty_pool->getStats()->setElapsed($partial_results->getStats()->getElapsed());
|
||||
}
|
||||
print ".\n";
|
||||
|
||||
$roster = $partial_results->getRoster();
|
||||
if ($roster) {
|
||||
foreach ($roster->getPlayers() as $player) {
|
||||
if (!array_key_exists($player->getId(), $player_pools[$empty_pool_name])) {
|
||||
$player_pools[$empty_pool_name][$player->getId()] = [];
|
||||
}
|
||||
foreach ($player->getAttributes() as $attr) {
|
||||
$player_pools[$empty_pool_name][$player->getId()][$attr->getName()] = $attr->getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($cfg['debug']) {
|
||||
$end = microtime(true);
|
||||
printf("\n'%s': count %06d | elapsed %0.3f\n", $empty_pool_name, count($player_pools[$empty_pool_name]), $end - $start);
|
||||
}
|
||||
}
|
||||
|
||||
#################################################################
|
||||
# Step 5 - Run custom matchmaking logic to try to find a match
|
||||
# This is in the file mmf.py
|
||||
$results = make_matches($profile_dict, $player_pools);
|
||||
#################################################################
|
||||
|
||||
# DEBUG
|
||||
if ($cfg['debug']) {
|
||||
print("======= match_properties\n");
|
||||
var_export($results);
|
||||
}
|
||||
|
||||
# Generate a MatchObject message to write to state storage with the results in it.
|
||||
$mo = new Messages\MatchObject([
|
||||
'id' => getenv('MMF_PROPOSAL_ID'),
|
||||
'properties' => json_encode($results)
|
||||
]);
|
||||
$mo->setPools($profile_pb->getPools());
|
||||
|
||||
# Access the rosters in dict form within the properties json.
|
||||
# It is stored at the key specified in the config file.
|
||||
$rosters_dict = $results;
|
||||
foreach (explode('.', $cfg['jsonkeys']['rosters']) as $partial_key) {
|
||||
$rosters_dict = $rosters_dict[$partial_key] ?? [];
|
||||
}
|
||||
|
||||
# Unmarshal the rosters into the MatchObject
|
||||
foreach ($rosters_dict as $roster) {
|
||||
$r = new Messages\Roster();
|
||||
$r->mergeFromJsonString(json_encode($roster));
|
||||
$mo->getRosters() []= $r;
|
||||
}
|
||||
|
||||
#DEBUG: writing to error key prevents evalutor run
|
||||
if ($cfg['debug']) {
|
||||
print("======== MMF results:\n");
|
||||
dump_pb_message($mo);
|
||||
}
|
||||
|
||||
# Step 6 - Write the outcome of the matchmaking logic back to state storage.
|
||||
# Step 7 - Remove the selected players from consideration by other MMFs.
|
||||
# CreateProposal does both of these for you, and some other items as well.
|
||||
list($result, $status) = $mmlogic_api->CreateProposal($mo)->wait();
|
||||
printf("======== MMF write to state storage: %s\n", $result->getSuccess() ? 'true' : 'false');
|
||||
dump_pb_message($result);
|
||||
|
||||
# [OPTIONAL] Step 8 - Export stats about this run.
|
||||
# TODO
|
||||
|
||||
?>
|
1
examples/functions/php/mmlogic-simple/matchmaker_config.json
Symbolic link
1
examples/functions/php/mmlogic-simple/matchmaker_config.json
Symbolic link
@ -0,0 +1 @@
|
||||
../../../../config/matchmaker_config.json
|
27
examples/functions/php/mmlogic-simple/mmf.php
Normal file
27
examples/functions/php/mmlogic-simple/mmf.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
function make_matches($profile_dict, $player_pools) {
|
||||
###########################################################################
|
||||
# This is the exciting part, and where most of your custom code would go! #
|
||||
###########################################################################
|
||||
|
||||
foreach ($profile_dict['properties']['rosters'] as &$roster) {
|
||||
foreach ($roster['players'] as &$player) {
|
||||
if (array_key_exists('pool', $player)) {
|
||||
$player['id'] = array_rand($player_pools[$player['pool']]);
|
||||
printf("Selected player %s from pool %s (strategy: RANDOM)\n",
|
||||
$player['id'],
|
||||
$player['pool']
|
||||
);
|
||||
} else {
|
||||
var_export($player);
|
||||
}
|
||||
}
|
||||
unset($player);
|
||||
}
|
||||
unset($roster);
|
||||
|
||||
return $profile_dict;
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
// GENERATED CODE -- DO NOT EDIT!
|
||||
|
||||
namespace Api;
|
||||
|
||||
/**
|
||||
*/
|
||||
class BackendClient extends \Grpc\BaseStub {
|
||||
|
||||
/**
|
||||
* @param string $hostname hostname
|
||||
* @param array $opts channel options
|
||||
* @param \Grpc\Channel $channel (optional) re-use channel object
|
||||
*/
|
||||
public function __construct($hostname, $opts, $channel = null) {
|
||||
parent::__construct($hostname, $opts, $channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run MMF once. Return a matchobject that fits this profile.
|
||||
* INPUT: MatchObject message with these fields populated:
|
||||
* - id
|
||||
* - properties
|
||||
* - [optional] roster, any fields you fill are available to your MMF.
|
||||
* - [optional] pools, any fields you fill are available to your MMF.
|
||||
* OUTPUT: MatchObject message with these fields populated:
|
||||
* - id
|
||||
* - properties
|
||||
* - error. Empty if no error was encountered
|
||||
* - rosters, if you choose to fill them in your MMF. (Recommended)
|
||||
* - pools, if you used the MMLogicAPI in your MMF. (Recommended, and provides stats)
|
||||
* @param \Messages\MatchObject $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function CreateMatch(\Messages\MatchObject $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_simpleRequest('/api.Backend/CreateMatch',
|
||||
$argument,
|
||||
['\Messages\MatchObject', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Continually run MMF and stream matchobjects that fit this profile until
|
||||
* client closes the connection. Same inputs/outputs as CreateMatch.
|
||||
* @param \Messages\MatchObject $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function ListMatches(\Messages\MatchObject $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_serverStreamRequest('/api.Backend/ListMatches',
|
||||
$argument,
|
||||
['\Messages\MatchObject', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a matchobject from state storage manually. (Matchobjects in state
|
||||
* storage will also automatically expire after a while)
|
||||
* INPUT: MatchObject message with the 'id' field populated.
|
||||
* (All other fields are ignored.)
|
||||
* @param \Messages\MatchObject $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function DeleteMatch(\Messages\MatchObject $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_simpleRequest('/api.Backend/DeleteMatch',
|
||||
$argument,
|
||||
['\Messages\Result', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call fors communication of connection info to players.
|
||||
*
|
||||
* Write the connection info for the list of players in the
|
||||
* Assignments.messages.Rosters to state storage. The FrontendAPI is
|
||||
* responsible for sending anything sent here to the game clients.
|
||||
* Sending a player to this function kicks off a process that removes
|
||||
* the player from future matchmaking functions by adding them to the
|
||||
* 'deindexed' player list and then deleting their player ID from state storage
|
||||
* indexes.
|
||||
* INPUT: Assignments message with these fields populated:
|
||||
* - connection_info, anything you write to this string is sent to Frontend API
|
||||
* - rosters. You can send any number of rosters, containing any number of
|
||||
* player messages. All players from all rosters will be sent the connection_info.
|
||||
* The only field in the Player object that is used by CreateAssignments is
|
||||
* the id field. All others are silently ignored.
|
||||
* @param \Messages\Assignments $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function CreateAssignments(\Messages\Assignments $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_simpleRequest('/api.Backend/CreateAssignments',
|
||||
$argument,
|
||||
['\Messages\Result', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove DGS connection info from state storage for players.
|
||||
* INPUT: Roster message with the 'players' field populated.
|
||||
* The only field in the Player object that is used by
|
||||
* DeleteAssignments is the 'id' field. All others are silently ignored. If
|
||||
* you need to delete multiple rosters, make multiple calls.
|
||||
* @param \Messages\Roster $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function DeleteAssignments(\Messages\Roster $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_simpleRequest('/api.Backend/DeleteAssignments',
|
||||
$argument,
|
||||
['\Messages\Result', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
// GENERATED CODE -- DO NOT EDIT!
|
||||
|
||||
// Original file comments:
|
||||
// TODO: In a future version, these messages will be moved/merged with those in om_messages.proto
|
||||
namespace Api;
|
||||
|
||||
/**
|
||||
*/
|
||||
class FrontendClient extends \Grpc\BaseStub {
|
||||
|
||||
/**
|
||||
* @param string $hostname hostname
|
||||
* @param array $opts channel options
|
||||
* @param \Grpc\Channel $channel (optional) re-use channel object
|
||||
*/
|
||||
public function __construct($hostname, $opts, $channel = null) {
|
||||
parent::__construct($hostname, $opts, $channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Api\Group $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function CreateRequest(\Api\Group $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_simpleRequest('/api.Frontend/CreateRequest',
|
||||
$argument,
|
||||
['\Messages\Result', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Api\Group $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function DeleteRequest(\Api\Group $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_simpleRequest('/api.Frontend/DeleteRequest',
|
||||
$argument,
|
||||
['\Messages\Result', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Api\PlayerId $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function GetAssignment(\Api\PlayerId $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_simpleRequest('/api.Frontend/GetAssignment',
|
||||
$argument,
|
||||
['\Messages\ConnectionInfo', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Api\PlayerId $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function DeleteAssignment(\Api\PlayerId $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_simpleRequest('/api.Frontend/DeleteAssignment',
|
||||
$argument,
|
||||
['\Messages\Result', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
}
|
102
examples/functions/php/mmlogic-simple/proto/Api/Group.php
Normal file
102
examples/functions/php/mmlogic-simple/proto/Api/Group.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/frontend.proto
|
||||
|
||||
namespace Api;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* Data structure for a group of players to pass to the matchmaking function.
|
||||
* Obviously, the group can be a group of one!
|
||||
*
|
||||
* Generated from protobuf message <code>api.Group</code>
|
||||
*/
|
||||
class Group extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* By convention, string of space-delimited playerIDs
|
||||
*
|
||||
* Generated from protobuf field <code>string id = 1;</code>
|
||||
*/
|
||||
private $id = '';
|
||||
/**
|
||||
* By convention, a JSON-encoded string
|
||||
*
|
||||
* Generated from protobuf field <code>string properties = 2;</code>
|
||||
*/
|
||||
private $properties = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type string $id
|
||||
* By convention, string of space-delimited playerIDs
|
||||
* @type string $properties
|
||||
* By convention, a JSON-encoded string
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Frontend::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, string of space-delimited playerIDs
|
||||
*
|
||||
* Generated from protobuf field <code>string id = 1;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, string of space-delimited playerIDs
|
||||
*
|
||||
* Generated from protobuf field <code>string id = 1;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setId($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->id = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, a JSON-encoded string
|
||||
*
|
||||
* Generated from protobuf field <code>string properties = 2;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getProperties()
|
||||
{
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, a JSON-encoded string
|
||||
*
|
||||
* Generated from protobuf field <code>string properties = 2;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setProperties($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->properties = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
// GENERATED CODE -- DO NOT EDIT!
|
||||
|
||||
namespace Api;
|
||||
|
||||
/**
|
||||
* The MMLogic API provides utility functions for common MMF functionality, such
|
||||
* as retreiving profiles and players from state storage, writing results to state storage,
|
||||
* and exposing metrics and statistics.
|
||||
*/
|
||||
class MmLogicClient extends \Grpc\BaseStub {
|
||||
|
||||
/**
|
||||
* @param string $hostname hostname
|
||||
* @param array $opts channel options
|
||||
* @param \Grpc\Channel $channel (optional) re-use channel object
|
||||
*/
|
||||
public function __construct($hostname, $opts, $channel = null) {
|
||||
parent::__construct($hostname, $opts, $channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send GetProfile a match object with the ID field populated, it will return a
|
||||
* 'filled' one.
|
||||
* Note: filters are assumed to have been checked for validity by the
|
||||
* backendapi when accepting a profile
|
||||
* @param \Messages\MatchObject $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function GetProfile(\Messages\MatchObject $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_simpleRequest('/api.MmLogic/GetProfile',
|
||||
$argument,
|
||||
['\Messages\MatchObject', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateProposal is called by MMFs that wish to write their results to
|
||||
* a proposed MatchObject, that can be sent out the Backend API once it has
|
||||
* been approved (by default, by the evaluator process).
|
||||
* - adds all players in all Rosters to the proposed player ignore list
|
||||
* - writes the proposed match to the provided key
|
||||
* - adds that key to the list of proposals to be considered
|
||||
* INPUT:
|
||||
* * TO RETURN A MATCHOBJECT AFTER A SUCCESSFUL MMF RUN
|
||||
* To create a match MatchObject message with these fields populated:
|
||||
* - id, set to the value of the MMF_PROPOSAL_ID env var
|
||||
* - properties
|
||||
* - error. You must explicitly set this to an empty string if your MMF
|
||||
* - roster, with the playerIDs filled in the 'players' repeated field.
|
||||
* - [optional] pools, set to the output from the 'GetPlayerPools' call,
|
||||
* will populate the pools with stats about how many players the filters
|
||||
* matched and how long the filters took to run, which will be sent out
|
||||
* the backend api along with your match results.
|
||||
* was successful.
|
||||
* * TO RETURN AN ERROR
|
||||
* To report a failure or error, send a MatchObject message with these
|
||||
* these fields populated:
|
||||
* - id, set to the value of the MMF_ERROR_ID env var.
|
||||
* - error, set to a string value describing the error your MMF encountered.
|
||||
* - [optional] properties, anything you put here is returned to the
|
||||
* backend along with your error.
|
||||
* - [optional] rosters, anything you put here is returned to the
|
||||
* backend along with your error.
|
||||
* - [optional] pools, set to the output from the 'GetPlayerPools' call,
|
||||
* will populate the pools with stats about how many players the filters
|
||||
* matched and how long the filters took to run, which will be sent out
|
||||
* the backend api along with your match results.
|
||||
* OUTPUT: a Result message with a boolean success value and an error string
|
||||
* if an error was encountered
|
||||
* @param \Messages\MatchObject $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function CreateProposal(\Messages\MatchObject $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_simpleRequest('/api.MmLogic/CreateProposal',
|
||||
$argument,
|
||||
['\Messages\Result', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Player listing and filtering functions
|
||||
*
|
||||
* RetrievePlayerPool gets the list of players that match every Filter in the
|
||||
* PlayerPool, and then removes all players it finds in the ignore list. It
|
||||
* combines the results, and returns the resulting player pool.
|
||||
* @param \Messages\PlayerPool $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function GetPlayerPool(\Messages\PlayerPool $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_serverStreamRequest('/api.MmLogic/GetPlayerPool',
|
||||
$argument,
|
||||
['\Messages\PlayerPool', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore List functions
|
||||
*
|
||||
* IlInput is an empty message reserved for future use.
|
||||
* @param \Messages\IlInput $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function GetAllIgnoredPlayers(\Messages\IlInput $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_simpleRequest('/api.MmLogic/GetAllIgnoredPlayers',
|
||||
$argument,
|
||||
['\Messages\Roster', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* RetrieveIgnoreList retrieves players from the ignore list specified in the
|
||||
* config file under 'ignoreLists.proposedPlayers.key'.
|
||||
* @param \Messages\IlInput $argument input argument
|
||||
* @param array $metadata metadata
|
||||
* @param array $options call options
|
||||
*/
|
||||
public function ListIgnoredPlayers(\Messages\IlInput $argument,
|
||||
$metadata = [], $options = []) {
|
||||
return $this->_simpleRequest('/api.MmLogic/ListIgnoredPlayers',
|
||||
$argument,
|
||||
['\Messages\Roster', 'decode'],
|
||||
$metadata, $options);
|
||||
}
|
||||
|
||||
}
|
65
examples/functions/php/mmlogic-simple/proto/Api/PlayerId.php
Normal file
65
examples/functions/php/mmlogic-simple/proto/Api/PlayerId.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/frontend.proto
|
||||
|
||||
namespace Api;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* Generated from protobuf message <code>api.PlayerId</code>
|
||||
*/
|
||||
class PlayerId extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* By convention, a UUID
|
||||
*
|
||||
* Generated from protobuf field <code>string id = 1;</code>
|
||||
*/
|
||||
private $id = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type string $id
|
||||
* By convention, a UUID
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Frontend::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, a UUID
|
||||
*
|
||||
* Generated from protobuf field <code>string id = 1;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, a UUID
|
||||
*
|
||||
* Generated from protobuf field <code>string id = 1;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setId($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->id = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/backend.proto
|
||||
|
||||
namespace GPBMetadata\Api\ProtobufSpec;
|
||||
|
||||
class Backend
|
||||
{
|
||||
public static $is_initialized = false;
|
||||
|
||||
public static function initOnce() {
|
||||
$pool = \Google\Protobuf\Internal\DescriptorPool::getGeneratedPool();
|
||||
|
||||
if (static::$is_initialized == true) {
|
||||
return;
|
||||
}
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
$pool->internalAddGeneratedFile(hex2bin(
|
||||
"0aa8030a1f6170692f70726f746f6275662d737065632f6261636b656e64" .
|
||||
"2e70726f746f120361706932be020a074261636b656e64123d0a0b437265" .
|
||||
"6174654d6174636812152e6d657373616765732e4d617463684f626a6563" .
|
||||
"741a152e6d657373616765732e4d617463684f626a6563742200123f0a0b" .
|
||||
"4c6973744d61746368657312152e6d657373616765732e4d617463684f62" .
|
||||
"6a6563741a152e6d657373616765732e4d617463684f626a656374220030" .
|
||||
"0112380a0b44656c6574654d6174636812152e6d657373616765732e4d61" .
|
||||
"7463684f626a6563741a102e6d657373616765732e526573756c74220012" .
|
||||
"3e0a1143726561746541737369676e6d656e747312152e6d657373616765" .
|
||||
"732e41737369676e6d656e74731a102e6d657373616765732e526573756c" .
|
||||
"74220012390a1144656c65746541737369676e6d656e747312102e6d6573" .
|
||||
"73616765732e526f737465721a102e6d657373616765732e526573756c74" .
|
||||
"220042375a356769746875622e636f6d2f476f6f676c65436c6f7564506c" .
|
||||
"6174666f726d2f6f70656e2d6d617463682f696e7465726e616c2f706262" .
|
||||
"0670726f746f33"
|
||||
));
|
||||
|
||||
static::$is_initialized = true;
|
||||
}
|
||||
}
|
||||
|
38
examples/functions/php/mmlogic-simple/proto/GPBMetadata/Api/ProtobufSpec/Frontend.php
Normal file
38
examples/functions/php/mmlogic-simple/proto/GPBMetadata/Api/ProtobufSpec/Frontend.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/frontend.proto
|
||||
|
||||
namespace GPBMetadata\Api\ProtobufSpec;
|
||||
|
||||
class Frontend
|
||||
{
|
||||
public static $is_initialized = false;
|
||||
|
||||
public static function initOnce() {
|
||||
$pool = \Google\Protobuf\Internal\DescriptorPool::getGeneratedPool();
|
||||
|
||||
if (static::$is_initialized == true) {
|
||||
return;
|
||||
}
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
$pool->internalAddGeneratedFile(hex2bin(
|
||||
"0a8b030a206170692f70726f746f6275662d737065632f66726f6e74656e" .
|
||||
"642e70726f746f120361706922270a0547726f7570120a0a026964180120" .
|
||||
"01280912120a0a70726f7065727469657318022001280922160a08506c61" .
|
||||
"7965724964120a0a02696418012001280932df010a0846726f6e74656e64" .
|
||||
"122f0a0d43726561746552657175657374120a2e6170692e47726f75701a" .
|
||||
"102e6d657373616765732e526573756c742200122f0a0d44656c65746552" .
|
||||
"657175657374120a2e6170692e47726f75701a102e6d657373616765732e" .
|
||||
"526573756c742200123a0a0d47657441737369676e6d656e74120d2e6170" .
|
||||
"692e506c6179657249641a182e6d657373616765732e436f6e6e65637469" .
|
||||
"6f6e496e666f220012350a1044656c65746541737369676e6d656e74120d" .
|
||||
"2e6170692e506c6179657249641a102e6d657373616765732e526573756c" .
|
||||
"74220042375a356769746875622e636f6d2f476f6f676c65436c6f756450" .
|
||||
"6c6174666f726d2f6f70656e2d6d617463682f696e7465726e616c2f7062" .
|
||||
"620670726f746f33"
|
||||
));
|
||||
|
||||
static::$is_initialized = true;
|
||||
}
|
||||
}
|
||||
|
54
examples/functions/php/mmlogic-simple/proto/GPBMetadata/Api/ProtobufSpec/Messages.php
Normal file
54
examples/functions/php/mmlogic-simple/proto/GPBMetadata/Api/ProtobufSpec/Messages.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace GPBMetadata\Api\ProtobufSpec;
|
||||
|
||||
class Messages
|
||||
{
|
||||
public static $is_initialized = false;
|
||||
|
||||
public static function initOnce() {
|
||||
$pool = \Google\Protobuf\Internal\DescriptorPool::getGeneratedPool();
|
||||
|
||||
if (static::$is_initialized == true) {
|
||||
return;
|
||||
}
|
||||
$pool->internalAddGeneratedFile(hex2bin(
|
||||
"0a9a070a206170692f70726f746f6275662d737065632f6d657373616765" .
|
||||
"732e70726f746f12086d657373616765732284010a0b4d617463684f626a" .
|
||||
"656374120a0a02696418012001280912120a0a70726f7065727469657318" .
|
||||
"0220012809120d0a056572726f7218032001280912210a07726f73746572" .
|
||||
"7318042003280b32102e6d657373616765732e526f7374657212230a0570" .
|
||||
"6f6f6c7318052003280b32142e6d657373616765732e506c61796572506f" .
|
||||
"6f6c22390a06526f73746572120c0a046e616d6518012001280912210a07" .
|
||||
"706c617965727318022003280b32102e6d657373616765732e506c617965" .
|
||||
"7222650a0646696c746572120c0a046e616d6518012001280912110a0961" .
|
||||
"7474726962757465180220012809120c0a046d617876180320012803120c" .
|
||||
"0a046d696e76180420012803121e0a05737461747318052001280b320f2e" .
|
||||
"6d657373616765732e537461747322270a055374617473120d0a05636f75" .
|
||||
"6e74180120012803120f0a07656c6170736564180220012801227f0a0a50" .
|
||||
"6c61796572506f6f6c120c0a046e616d6518012001280912210a0766696c" .
|
||||
"7465727318022003280b32102e6d657373616765732e46696c7465721220" .
|
||||
"0a06726f7374657218032001280b32102e6d657373616765732e526f7374" .
|
||||
"6572121e0a05737461747318042001280b320f2e6d657373616765732e53" .
|
||||
"746174732290010a06506c61796572120a0a02696418012001280912120a" .
|
||||
"0a70726f70657274696573180220012809120c0a04706f6f6c1803200128" .
|
||||
"09122e0a0a6174747269627574657318042003280b321a2e6d6573736167" .
|
||||
"65732e506c617965722e4174747269627574651a280a0941747472696275" .
|
||||
"7465120c0a046e616d65180120012809120d0a0576616c75651802200128" .
|
||||
"0322280a06526573756c74120f0a0773756363657373180120012808120d" .
|
||||
"0a056572726f7218022001280922090a07496c496e707574222b0a0e436f" .
|
||||
"6e6e656374696f6e496e666f12190a11636f6e6e656374696f6e5f737472" .
|
||||
"696e6718012001280922630a0b41737369676e6d656e747312210a07726f" .
|
||||
"737465727318012003280b32102e6d657373616765732e526f7374657212" .
|
||||
"310a0f636f6e6e656374696f6e5f696e666f18022001280b32182e6d6573" .
|
||||
"73616765732e436f6e6e656374696f6e496e666f42375a35676974687562" .
|
||||
"2e636f6d2f476f6f676c65436c6f7564506c6174666f726d2f6f70656e2d" .
|
||||
"6d617463682f696e7465726e616c2f7062620670726f746f33"
|
||||
));
|
||||
|
||||
static::$is_initialized = true;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/mmlogic.proto
|
||||
|
||||
namespace GPBMetadata\Api\ProtobufSpec;
|
||||
|
||||
class Mmlogic
|
||||
{
|
||||
public static $is_initialized = false;
|
||||
|
||||
public static function initOnce() {
|
||||
$pool = \Google\Protobuf\Internal\DescriptorPool::getGeneratedPool();
|
||||
|
||||
if (static::$is_initialized == true) {
|
||||
return;
|
||||
}
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
$pool->internalAddGeneratedFile(hex2bin(
|
||||
"0aab030a1f6170692f70726f746f6275662d737065632f6d6d6c6f676963" .
|
||||
"2e70726f746f120361706932c1020a074d6d4c6f676963123c0a0a476574" .
|
||||
"50726f66696c6512152e6d657373616765732e4d617463684f626a656374" .
|
||||
"1a152e6d657373616765732e4d617463684f626a6563742200123b0a0e43" .
|
||||
"726561746550726f706f73616c12152e6d657373616765732e4d61746368" .
|
||||
"4f626a6563741a102e6d657373616765732e526573756c742200123f0a0d" .
|
||||
"476574506c61796572506f6f6c12142e6d657373616765732e506c617965" .
|
||||
"72506f6f6c1a142e6d657373616765732e506c61796572506f6f6c220030" .
|
||||
"01123d0a14476574416c6c49676e6f726564506c617965727312112e6d65" .
|
||||
"7373616765732e496c496e7075741a102e6d657373616765732e526f7374" .
|
||||
"65722200123b0a124c69737449676e6f726564506c617965727312112e6d" .
|
||||
"657373616765732e496c496e7075741a102e6d657373616765732e526f73" .
|
||||
"746572220042375a356769746875622e636f6d2f476f6f676c65436c6f75" .
|
||||
"64506c6174666f726d2f6f70656e2d6d617463682f696e7465726e616c2f" .
|
||||
"7062620670726f746f33"
|
||||
));
|
||||
|
||||
static::$is_initialized = true;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace Messages;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* Generated from protobuf message <code>messages.Assignments</code>
|
||||
*/
|
||||
class Assignments extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* Generated from protobuf field <code>repeated .messages.Roster rosters = 1;</code>
|
||||
*/
|
||||
private $rosters;
|
||||
/**
|
||||
* Generated from protobuf field <code>.messages.ConnectionInfo connection_info = 2;</code>
|
||||
*/
|
||||
private $connection_info = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type \Messages\Roster[]|\Google\Protobuf\Internal\RepeatedField $rosters
|
||||
* @type \Messages\ConnectionInfo $connection_info
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated from protobuf field <code>repeated .messages.Roster rosters = 1;</code>
|
||||
* @return \Google\Protobuf\Internal\RepeatedField
|
||||
*/
|
||||
public function getRosters()
|
||||
{
|
||||
return $this->rosters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated from protobuf field <code>repeated .messages.Roster rosters = 1;</code>
|
||||
* @param \Messages\Roster[]|\Google\Protobuf\Internal\RepeatedField $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setRosters($var)
|
||||
{
|
||||
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Messages\Roster::class);
|
||||
$this->rosters = $arr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated from protobuf field <code>.messages.ConnectionInfo connection_info = 2;</code>
|
||||
* @return \Messages\ConnectionInfo
|
||||
*/
|
||||
public function getConnectionInfo()
|
||||
{
|
||||
return $this->connection_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated from protobuf field <code>.messages.ConnectionInfo connection_info = 2;</code>
|
||||
* @param \Messages\ConnectionInfo $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setConnectionInfo($var)
|
||||
{
|
||||
GPBUtil::checkMessage($var, \Messages\ConnectionInfo::class);
|
||||
$this->connection_info = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace Messages;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* Simple message used to pass the connection string for the DGS to the player.
|
||||
* DEPRECATED: Likely to be integrated into another protobuf message in a future version.
|
||||
*
|
||||
* Generated from protobuf message <code>messages.ConnectionInfo</code>
|
||||
*/
|
||||
class ConnectionInfo extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* Passed by the matchmaker to game clients without modification.
|
||||
*
|
||||
* Generated from protobuf field <code>string connection_string = 1;</code>
|
||||
*/
|
||||
private $connection_string = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type string $connection_string
|
||||
* Passed by the matchmaker to game clients without modification.
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passed by the matchmaker to game clients without modification.
|
||||
*
|
||||
* Generated from protobuf field <code>string connection_string = 1;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getConnectionString()
|
||||
{
|
||||
return $this->connection_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passed by the matchmaker to game clients without modification.
|
||||
*
|
||||
* Generated from protobuf field <code>string connection_string = 1;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setConnectionString($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->connection_string = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
203
examples/functions/php/mmlogic-simple/proto/Messages/Filter.php
Normal file
203
examples/functions/php/mmlogic-simple/proto/Messages/Filter.php
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace Messages;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* A 'hard' filter to apply to the player pool.
|
||||
*
|
||||
* Generated from protobuf message <code>messages.Filter</code>
|
||||
*/
|
||||
class Filter extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* Arbitrary developer-chosen, human-readable name of this filter. Appears in logs and metrics.
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 1;</code>
|
||||
*/
|
||||
private $name = '';
|
||||
/**
|
||||
* Name of the player attribute this filter operates on.
|
||||
*
|
||||
* Generated from protobuf field <code>string attribute = 2;</code>
|
||||
*/
|
||||
private $attribute = '';
|
||||
/**
|
||||
* Maximum value. Defaults to positive infinity (any value above minv).
|
||||
*
|
||||
* Generated from protobuf field <code>int64 maxv = 3;</code>
|
||||
*/
|
||||
private $maxv = 0;
|
||||
/**
|
||||
* Minimum value. Defaults to 0.
|
||||
*
|
||||
* Generated from protobuf field <code>int64 minv = 4;</code>
|
||||
*/
|
||||
private $minv = 0;
|
||||
/**
|
||||
* Statistics for the last time the filter was applied.
|
||||
*
|
||||
* Generated from protobuf field <code>.messages.Stats stats = 5;</code>
|
||||
*/
|
||||
private $stats = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type string $name
|
||||
* Arbitrary developer-chosen, human-readable name of this filter. Appears in logs and metrics.
|
||||
* @type string $attribute
|
||||
* Name of the player attribute this filter operates on.
|
||||
* @type int|string $maxv
|
||||
* Maximum value. Defaults to positive infinity (any value above minv).
|
||||
* @type int|string $minv
|
||||
* Minimum value. Defaults to 0.
|
||||
* @type \Messages\Stats $stats
|
||||
* Statistics for the last time the filter was applied.
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Arbitrary developer-chosen, human-readable name of this filter. Appears in logs and metrics.
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 1;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arbitrary developer-chosen, human-readable name of this filter. Appears in logs and metrics.
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 1;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->name = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the player attribute this filter operates on.
|
||||
*
|
||||
* Generated from protobuf field <code>string attribute = 2;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getAttribute()
|
||||
{
|
||||
return $this->attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the player attribute this filter operates on.
|
||||
*
|
||||
* Generated from protobuf field <code>string attribute = 2;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setAttribute($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->attribute = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum value. Defaults to positive infinity (any value above minv).
|
||||
*
|
||||
* Generated from protobuf field <code>int64 maxv = 3;</code>
|
||||
* @return int|string
|
||||
*/
|
||||
public function getMaxv()
|
||||
{
|
||||
return $this->maxv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum value. Defaults to positive infinity (any value above minv).
|
||||
*
|
||||
* Generated from protobuf field <code>int64 maxv = 3;</code>
|
||||
* @param int|string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setMaxv($var)
|
||||
{
|
||||
GPBUtil::checkInt64($var);
|
||||
$this->maxv = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum value. Defaults to 0.
|
||||
*
|
||||
* Generated from protobuf field <code>int64 minv = 4;</code>
|
||||
* @return int|string
|
||||
*/
|
||||
public function getMinv()
|
||||
{
|
||||
return $this->minv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum value. Defaults to 0.
|
||||
*
|
||||
* Generated from protobuf field <code>int64 minv = 4;</code>
|
||||
* @param int|string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setMinv($var)
|
||||
{
|
||||
GPBUtil::checkInt64($var);
|
||||
$this->minv = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistics for the last time the filter was applied.
|
||||
*
|
||||
* Generated from protobuf field <code>.messages.Stats stats = 5;</code>
|
||||
* @return \Messages\Stats
|
||||
*/
|
||||
public function getStats()
|
||||
{
|
||||
return $this->stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistics for the last time the filter was applied.
|
||||
*
|
||||
* Generated from protobuf field <code>.messages.Stats stats = 5;</code>
|
||||
* @param \Messages\Stats $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setStats($var)
|
||||
{
|
||||
GPBUtil::checkMessage($var, \Messages\Stats::class);
|
||||
$this->stats = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace Messages;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* IlInput is an empty message reserved for future use.
|
||||
*
|
||||
* Generated from protobuf message <code>messages.IlInput</code>
|
||||
*/
|
||||
class IlInput extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,213 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace Messages;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* Open Match's internal representation and wire protocol format for "MatchObjects".
|
||||
* In order to request a match using the Backend API, your backend code should generate
|
||||
* a new MatchObject with an ID and properties filled in (for more details about valid
|
||||
* values for these fields, see the documentation). Open Match then sends the Match
|
||||
* Object through to your matchmaking function, where you add players to 'rosters' and
|
||||
* store any schemaless data you wish in the 'properties' field. The MatchObject
|
||||
* is then sent, populated, out through the Backend API to your backend code.
|
||||
*
|
||||
* MatchObjects contain a number of fields, but many gRPC calls that take a
|
||||
* MatchObject as input only require a few of them to be filled in. Check the
|
||||
* gRPC function in question for more details.
|
||||
*
|
||||
* Generated from protobuf message <code>messages.MatchObject</code>
|
||||
*/
|
||||
class MatchObject extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* By convention, a UUID
|
||||
*
|
||||
* Generated from protobuf field <code>string id = 1;</code>
|
||||
*/
|
||||
private $id = '';
|
||||
/**
|
||||
* By convention, a JSON-encoded string
|
||||
*
|
||||
* Generated from protobuf field <code>string properties = 2;</code>
|
||||
*/
|
||||
private $properties = '';
|
||||
/**
|
||||
* Last error encountered.
|
||||
*
|
||||
* Generated from protobuf field <code>string error = 3;</code>
|
||||
*/
|
||||
private $error = '';
|
||||
/**
|
||||
* Rosters of players.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.Roster rosters = 4;</code>
|
||||
*/
|
||||
private $rosters;
|
||||
/**
|
||||
* 'Hard' filters, and the players who match them.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.PlayerPool pools = 5;</code>
|
||||
*/
|
||||
private $pools;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type string $id
|
||||
* By convention, a UUID
|
||||
* @type string $properties
|
||||
* By convention, a JSON-encoded string
|
||||
* @type string $error
|
||||
* Last error encountered.
|
||||
* @type \Messages\Roster[]|\Google\Protobuf\Internal\RepeatedField $rosters
|
||||
* Rosters of players.
|
||||
* @type \Messages\PlayerPool[]|\Google\Protobuf\Internal\RepeatedField $pools
|
||||
* 'Hard' filters, and the players who match them.
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, a UUID
|
||||
*
|
||||
* Generated from protobuf field <code>string id = 1;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, a UUID
|
||||
*
|
||||
* Generated from protobuf field <code>string id = 1;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setId($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->id = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, a JSON-encoded string
|
||||
*
|
||||
* Generated from protobuf field <code>string properties = 2;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getProperties()
|
||||
{
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, a JSON-encoded string
|
||||
*
|
||||
* Generated from protobuf field <code>string properties = 2;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setProperties($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->properties = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Last error encountered.
|
||||
*
|
||||
* Generated from protobuf field <code>string error = 3;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Last error encountered.
|
||||
*
|
||||
* Generated from protobuf field <code>string error = 3;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setError($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->error = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rosters of players.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.Roster rosters = 4;</code>
|
||||
* @return \Google\Protobuf\Internal\RepeatedField
|
||||
*/
|
||||
public function getRosters()
|
||||
{
|
||||
return $this->rosters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rosters of players.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.Roster rosters = 4;</code>
|
||||
* @param \Messages\Roster[]|\Google\Protobuf\Internal\RepeatedField $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setRosters($var)
|
||||
{
|
||||
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Messages\Roster::class);
|
||||
$this->rosters = $arr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Hard' filters, and the players who match them.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.PlayerPool pools = 5;</code>
|
||||
* @return \Google\Protobuf\Internal\RepeatedField
|
||||
*/
|
||||
public function getPools()
|
||||
{
|
||||
return $this->pools;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Hard' filters, and the players who match them.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.PlayerPool pools = 5;</code>
|
||||
* @param \Messages\PlayerPool[]|\Google\Protobuf\Internal\RepeatedField $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setPools($var)
|
||||
{
|
||||
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Messages\PlayerPool::class);
|
||||
$this->pools = $arr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
169
examples/functions/php/mmlogic-simple/proto/Messages/Player.php
Normal file
169
examples/functions/php/mmlogic-simple/proto/Messages/Player.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace Messages;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* Data structure to hold details about a player
|
||||
*
|
||||
* Generated from protobuf message <code>messages.Player</code>
|
||||
*/
|
||||
class Player extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* By convention, a UUID
|
||||
*
|
||||
* Generated from protobuf field <code>string id = 1;</code>
|
||||
*/
|
||||
private $id = '';
|
||||
/**
|
||||
* By convention, a JSON-encoded string
|
||||
*
|
||||
* Generated from protobuf field <code>string properties = 2;</code>
|
||||
*/
|
||||
private $properties = '';
|
||||
/**
|
||||
* Optionally used to specify the PlayerPool in which to find a player.
|
||||
*
|
||||
* Generated from protobuf field <code>string pool = 3;</code>
|
||||
*/
|
||||
private $pool = '';
|
||||
/**
|
||||
* Attributes of this player.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.Player.Attribute attributes = 4;</code>
|
||||
*/
|
||||
private $attributes;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type string $id
|
||||
* By convention, a UUID
|
||||
* @type string $properties
|
||||
* By convention, a JSON-encoded string
|
||||
* @type string $pool
|
||||
* Optionally used to specify the PlayerPool in which to find a player.
|
||||
* @type \Messages\Player\Attribute[]|\Google\Protobuf\Internal\RepeatedField $attributes
|
||||
* Attributes of this player.
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, a UUID
|
||||
*
|
||||
* Generated from protobuf field <code>string id = 1;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, a UUID
|
||||
*
|
||||
* Generated from protobuf field <code>string id = 1;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setId($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->id = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, a JSON-encoded string
|
||||
*
|
||||
* Generated from protobuf field <code>string properties = 2;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getProperties()
|
||||
{
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* By convention, a JSON-encoded string
|
||||
*
|
||||
* Generated from protobuf field <code>string properties = 2;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setProperties($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->properties = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally used to specify the PlayerPool in which to find a player.
|
||||
*
|
||||
* Generated from protobuf field <code>string pool = 3;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getPool()
|
||||
{
|
||||
return $this->pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally used to specify the PlayerPool in which to find a player.
|
||||
*
|
||||
* Generated from protobuf field <code>string pool = 3;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setPool($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->pool = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attributes of this player.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.Player.Attribute attributes = 4;</code>
|
||||
* @return \Google\Protobuf\Internal\RepeatedField
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attributes of this player.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.Player.Attribute attributes = 4;</code>
|
||||
* @param \Messages\Player\Attribute[]|\Google\Protobuf\Internal\RepeatedField $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setAttributes($var)
|
||||
{
|
||||
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Messages\Player\Attribute::class);
|
||||
$this->attributes = $arr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace Messages\Player;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* Generated from protobuf message <code>messages.Player.Attribute</code>
|
||||
*/
|
||||
class Attribute extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* Name should match a Filter.attribute field.
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 1;</code>
|
||||
*/
|
||||
private $name = '';
|
||||
/**
|
||||
* Generated from protobuf field <code>int64 value = 2;</code>
|
||||
*/
|
||||
private $value = 0;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type string $name
|
||||
* Name should match a Filter.attribute field.
|
||||
* @type int|string $value
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Name should match a Filter.attribute field.
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 1;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name should match a Filter.attribute field.
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 1;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->name = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated from protobuf field <code>int64 value = 2;</code>
|
||||
* @return int|string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated from protobuf field <code>int64 value = 2;</code>
|
||||
* @param int|string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setValue($var)
|
||||
{
|
||||
GPBUtil::checkInt64($var);
|
||||
$this->value = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Adding a class alias for backwards compatibility with the previous class name.
|
||||
class_alias(Attribute::class, \Messages\Player_Attribute::class);
|
||||
|
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace Messages;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* PlayerPools are defined by a set of 'hard' filters, and can be filled in
|
||||
* with the players that match those filters.
|
||||
* PlayerPools contain a number of fields, but many gRPC calls that take a
|
||||
* PlayerPool as input only require a few of them to be filled in. Check the
|
||||
* gRPC function in question for more details.
|
||||
*
|
||||
* Generated from protobuf message <code>messages.PlayerPool</code>
|
||||
*/
|
||||
class PlayerPool extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* Arbitrary developer-chosen, human-readable string.
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 1;</code>
|
||||
*/
|
||||
private $name = '';
|
||||
/**
|
||||
* Filters are logical AND-ed (a player must match every filter).
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.Filter filters = 2;</code>
|
||||
*/
|
||||
private $filters;
|
||||
/**
|
||||
* Roster of players that match all filters.
|
||||
*
|
||||
* Generated from protobuf field <code>.messages.Roster roster = 3;</code>
|
||||
*/
|
||||
private $roster = null;
|
||||
/**
|
||||
* Statisticss for the last time this Pool was retrieved from state storage.
|
||||
*
|
||||
* Generated from protobuf field <code>.messages.Stats stats = 4;</code>
|
||||
*/
|
||||
private $stats = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type string $name
|
||||
* Arbitrary developer-chosen, human-readable string.
|
||||
* @type \Messages\Filter[]|\Google\Protobuf\Internal\RepeatedField $filters
|
||||
* Filters are logical AND-ed (a player must match every filter).
|
||||
* @type \Messages\Roster $roster
|
||||
* Roster of players that match all filters.
|
||||
* @type \Messages\Stats $stats
|
||||
* Statisticss for the last time this Pool was retrieved from state storage.
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Arbitrary developer-chosen, human-readable string.
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 1;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arbitrary developer-chosen, human-readable string.
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 1;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->name = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters are logical AND-ed (a player must match every filter).
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.Filter filters = 2;</code>
|
||||
* @return \Google\Protobuf\Internal\RepeatedField
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters are logical AND-ed (a player must match every filter).
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.Filter filters = 2;</code>
|
||||
* @param \Messages\Filter[]|\Google\Protobuf\Internal\RepeatedField $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setFilters($var)
|
||||
{
|
||||
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Messages\Filter::class);
|
||||
$this->filters = $arr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Roster of players that match all filters.
|
||||
*
|
||||
* Generated from protobuf field <code>.messages.Roster roster = 3;</code>
|
||||
* @return \Messages\Roster
|
||||
*/
|
||||
public function getRoster()
|
||||
{
|
||||
return $this->roster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Roster of players that match all filters.
|
||||
*
|
||||
* Generated from protobuf field <code>.messages.Roster roster = 3;</code>
|
||||
* @param \Messages\Roster $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setRoster($var)
|
||||
{
|
||||
GPBUtil::checkMessage($var, \Messages\Roster::class);
|
||||
$this->roster = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Statisticss for the last time this Pool was retrieved from state storage.
|
||||
*
|
||||
* Generated from protobuf field <code>.messages.Stats stats = 4;</code>
|
||||
* @return \Messages\Stats
|
||||
*/
|
||||
public function getStats()
|
||||
{
|
||||
return $this->stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Statisticss for the last time this Pool was retrieved from state storage.
|
||||
*
|
||||
* Generated from protobuf field <code>.messages.Stats stats = 4;</code>
|
||||
* @param \Messages\Stats $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setStats($var)
|
||||
{
|
||||
GPBUtil::checkMessage($var, \Messages\Stats::class);
|
||||
$this->stats = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace Messages;
|
||||
|
||||
if (false) {
|
||||
/**
|
||||
* This class is deprecated. Use Messages\Player\Attribute instead.
|
||||
* @deprecated
|
||||
*/
|
||||
class Player_Attribute {}
|
||||
}
|
||||
class_exists(Player\Attribute::class);
|
||||
@trigger_error('Messages\Player_Attribute is deprecated and will be removed in the next major release. Use Messages\Player\Attribute instead', E_USER_DEPRECATED);
|
||||
|
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace Messages;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* Simple message to return success/failure and error status.
|
||||
*
|
||||
* Generated from protobuf message <code>messages.Result</code>
|
||||
*/
|
||||
class Result extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* Generated from protobuf field <code>bool success = 1;</code>
|
||||
*/
|
||||
private $success = false;
|
||||
/**
|
||||
* Generated from protobuf field <code>string error = 2;</code>
|
||||
*/
|
||||
private $error = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type bool $success
|
||||
* @type string $error
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated from protobuf field <code>bool success = 1;</code>
|
||||
* @return bool
|
||||
*/
|
||||
public function getSuccess()
|
||||
{
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated from protobuf field <code>bool success = 1;</code>
|
||||
* @param bool $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setSuccess($var)
|
||||
{
|
||||
GPBUtil::checkBool($var);
|
||||
$this->success = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated from protobuf field <code>string error = 2;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated from protobuf field <code>string error = 2;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setError($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->error = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
101
examples/functions/php/mmlogic-simple/proto/Messages/Roster.php
Normal file
101
examples/functions/php/mmlogic-simple/proto/Messages/Roster.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace Messages;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* Data structure to hold a list of players in a match.
|
||||
*
|
||||
* Generated from protobuf message <code>messages.Roster</code>
|
||||
*/
|
||||
class Roster extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* Arbitrary developer-chosen, human-readable string. By convention, set to team name.
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 1;</code>
|
||||
*/
|
||||
private $name = '';
|
||||
/**
|
||||
* Player profiles on this roster.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.Player players = 2;</code>
|
||||
*/
|
||||
private $players;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type string $name
|
||||
* Arbitrary developer-chosen, human-readable string. By convention, set to team name.
|
||||
* @type \Messages\Player[]|\Google\Protobuf\Internal\RepeatedField $players
|
||||
* Player profiles on this roster.
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Arbitrary developer-chosen, human-readable string. By convention, set to team name.
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 1;</code>
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arbitrary developer-chosen, human-readable string. By convention, set to team name.
|
||||
*
|
||||
* Generated from protobuf field <code>string name = 1;</code>
|
||||
* @param string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($var)
|
||||
{
|
||||
GPBUtil::checkString($var, True);
|
||||
$this->name = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Player profiles on this roster.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.Player players = 2;</code>
|
||||
* @return \Google\Protobuf\Internal\RepeatedField
|
||||
*/
|
||||
public function getPlayers()
|
||||
{
|
||||
return $this->players;
|
||||
}
|
||||
|
||||
/**
|
||||
* Player profiles on this roster.
|
||||
*
|
||||
* Generated from protobuf field <code>repeated .messages.Player players = 2;</code>
|
||||
* @param \Messages\Player[]|\Google\Protobuf\Internal\RepeatedField $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setPlayers($var)
|
||||
{
|
||||
$arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Messages\Player::class);
|
||||
$this->players = $arr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
101
examples/functions/php/mmlogic-simple/proto/Messages/Stats.php
Normal file
101
examples/functions/php/mmlogic-simple/proto/Messages/Stats.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
|
||||
namespace Messages;
|
||||
|
||||
use Google\Protobuf\Internal\GPBType;
|
||||
use Google\Protobuf\Internal\RepeatedField;
|
||||
use Google\Protobuf\Internal\GPBUtil;
|
||||
|
||||
/**
|
||||
* Holds statistics
|
||||
*
|
||||
* Generated from protobuf message <code>messages.Stats</code>
|
||||
*/
|
||||
class Stats extends \Google\Protobuf\Internal\Message
|
||||
{
|
||||
/**
|
||||
* Number of results.
|
||||
*
|
||||
* Generated from protobuf field <code>int64 count = 1;</code>
|
||||
*/
|
||||
private $count = 0;
|
||||
/**
|
||||
* How long it took to get the results.
|
||||
*
|
||||
* Generated from protobuf field <code>double elapsed = 2;</code>
|
||||
*/
|
||||
private $elapsed = 0.0;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data {
|
||||
* Optional. Data for populating the Message object.
|
||||
*
|
||||
* @type int|string $count
|
||||
* Number of results.
|
||||
* @type float $elapsed
|
||||
* How long it took to get the results.
|
||||
* }
|
||||
*/
|
||||
public function __construct($data = NULL) {
|
||||
\GPBMetadata\Api\ProtobufSpec\Messages::initOnce();
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of results.
|
||||
*
|
||||
* Generated from protobuf field <code>int64 count = 1;</code>
|
||||
* @return int|string
|
||||
*/
|
||||
public function getCount()
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of results.
|
||||
*
|
||||
* Generated from protobuf field <code>int64 count = 1;</code>
|
||||
* @param int|string $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setCount($var)
|
||||
{
|
||||
GPBUtil::checkInt64($var);
|
||||
$this->count = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* How long it took to get the results.
|
||||
*
|
||||
* Generated from protobuf field <code>double elapsed = 2;</code>
|
||||
* @return float
|
||||
*/
|
||||
public function getElapsed()
|
||||
{
|
||||
return $this->elapsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* How long it took to get the results.
|
||||
*
|
||||
* Generated from protobuf field <code>double elapsed = 2;</code>
|
||||
* @param float $var
|
||||
* @return $this
|
||||
*/
|
||||
public function setElapsed($var)
|
||||
{
|
||||
GPBUtil::checkDouble($var);
|
||||
$this->elapsed = $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,709 @@
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/messages.proto
|
||||
#Copyright 2018 Google LLC
|
||||
#Licensed under the Apache License, Version 2.0 (the "License");
|
||||
#you may not use this file except in compliance with the License.
|
||||
#You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
#Unless required by applicable law or agreed to in writing, software
|
||||
#distributed under the License is distributed on an "AS IS" BASIS,
|
||||
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#See the License for the specific language governing permissions and
|
||||
#limitations under the License.
|
||||
|
||||
|
||||
import sys
|
||||
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor.FileDescriptor(
|
||||
name='api/protobuf-spec/messages.proto',
|
||||
package='messages',
|
||||
syntax='proto3',
|
||||
serialized_options=_b('Z5github.com/GoogleCloudPlatform/open-match/internal/pb'),
|
||||
serialized_pb=_b('\n api/protobuf-spec/messages.proto\x12\x08messages\"\\\n\x07Profile\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nproperties\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12#\n\x05pools\x18\x04 \x03(\x0b\x32\x14.messages.PlayerPool\"\x84\x01\n\x0bMatchObject\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nproperties\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12!\n\x07rosters\x18\x04 \x03(\x0b\x32\x10.messages.Roster\x12#\n\x05pools\x18\x05 \x03(\x0b\x32\x14.messages.PlayerPool\"9\n\x06Roster\x12\x0c\n\x04name\x18\x01 \x01(\t\x12!\n\x07players\x18\x02 \x03(\x0b\x32\x10.messages.Player\"e\n\x06\x46ilter\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x11\n\tattribute\x18\x02 \x01(\t\x12\x0c\n\x04maxv\x18\x03 \x01(\x03\x12\x0c\n\x04minv\x18\x04 \x01(\x03\x12\x1e\n\x05stats\x18\x05 \x01(\x0b\x32\x0f.messages.Stats\"\'\n\x05Stats\x12\r\n\x05\x63ount\x18\x01 \x01(\x03\x12\x0f\n\x07\x65lapsed\x18\x02 \x01(\x01\"\x7f\n\nPlayerPool\x12\x0c\n\x04name\x18\x01 \x01(\t\x12!\n\x07\x66ilters\x18\x02 \x03(\x0b\x32\x10.messages.Filter\x12 \n\x06roster\x18\x03 \x01(\x0b\x32\x10.messages.Roster\x12\x1e\n\x05stats\x18\x04 \x01(\x0b\x32\x0f.messages.Stats\"\x90\x01\n\x06Player\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nproperties\x18\x02 \x01(\t\x12\x0c\n\x04pool\x18\x03 \x01(\t\x12.\n\nattributes\x18\x04 \x03(\x0b\x32\x1a.messages.Player.Attribute\x1a(\n\tAttribute\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03\"(\n\x06Result\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"\t\n\x07IlInput\"\x17\n\tTimestamp\x12\n\n\x02ts\x18\x01 \x01(\x03\"+\n\x0e\x43onnectionInfo\x12\x19\n\x11\x63onnection_string\x18\x01 \x01(\t\"c\n\x0b\x41ssignments\x12!\n\x07rosters\x18\x01 \x03(\x0b\x32\x10.messages.Roster\x12\x31\n\x0f\x63onnection_info\x18\x02 \x01(\x0b\x32\x18.messages.ConnectionInfoB7Z5github.com/GoogleCloudPlatform/open-match/internal/pbb\x06proto3')
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
_PROFILE = _descriptor.Descriptor(
|
||||
name='Profile',
|
||||
full_name='messages.Profile',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='id', full_name='messages.Profile.id', index=0,
|
||||
number=1, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='properties', full_name='messages.Profile.properties', index=1,
|
||||
number=2, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='name', full_name='messages.Profile.name', index=2,
|
||||
number=3, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='pools', full_name='messages.Profile.pools', index=3,
|
||||
number=4, type=11, cpp_type=10, label=3,
|
||||
has_default_value=False, default_value=[],
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=46,
|
||||
serialized_end=138,
|
||||
)
|
||||
|
||||
|
||||
_MATCHOBJECT = _descriptor.Descriptor(
|
||||
name='MatchObject',
|
||||
full_name='messages.MatchObject',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='id', full_name='messages.MatchObject.id', index=0,
|
||||
number=1, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='properties', full_name='messages.MatchObject.properties', index=1,
|
||||
number=2, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='error', full_name='messages.MatchObject.error', index=2,
|
||||
number=3, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='rosters', full_name='messages.MatchObject.rosters', index=3,
|
||||
number=4, type=11, cpp_type=10, label=3,
|
||||
has_default_value=False, default_value=[],
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='pools', full_name='messages.MatchObject.pools', index=4,
|
||||
number=5, type=11, cpp_type=10, label=3,
|
||||
has_default_value=False, default_value=[],
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=141,
|
||||
serialized_end=273,
|
||||
)
|
||||
|
||||
|
||||
_ROSTER = _descriptor.Descriptor(
|
||||
name='Roster',
|
||||
full_name='messages.Roster',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='name', full_name='messages.Roster.name', index=0,
|
||||
number=1, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='players', full_name='messages.Roster.players', index=1,
|
||||
number=2, type=11, cpp_type=10, label=3,
|
||||
has_default_value=False, default_value=[],
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=275,
|
||||
serialized_end=332,
|
||||
)
|
||||
|
||||
|
||||
_FILTER = _descriptor.Descriptor(
|
||||
name='Filter',
|
||||
full_name='messages.Filter',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='name', full_name='messages.Filter.name', index=0,
|
||||
number=1, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='attribute', full_name='messages.Filter.attribute', index=1,
|
||||
number=2, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='maxv', full_name='messages.Filter.maxv', index=2,
|
||||
number=3, type=3, cpp_type=2, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='minv', full_name='messages.Filter.minv', index=3,
|
||||
number=4, type=3, cpp_type=2, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='stats', full_name='messages.Filter.stats', index=4,
|
||||
number=5, type=11, cpp_type=10, label=1,
|
||||
has_default_value=False, default_value=None,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=334,
|
||||
serialized_end=435,
|
||||
)
|
||||
|
||||
|
||||
_STATS = _descriptor.Descriptor(
|
||||
name='Stats',
|
||||
full_name='messages.Stats',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='count', full_name='messages.Stats.count', index=0,
|
||||
number=1, type=3, cpp_type=2, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='elapsed', full_name='messages.Stats.elapsed', index=1,
|
||||
number=2, type=1, cpp_type=5, label=1,
|
||||
has_default_value=False, default_value=float(0),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=437,
|
||||
serialized_end=476,
|
||||
)
|
||||
|
||||
|
||||
_PLAYERPOOL = _descriptor.Descriptor(
|
||||
name='PlayerPool',
|
||||
full_name='messages.PlayerPool',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='name', full_name='messages.PlayerPool.name', index=0,
|
||||
number=1, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='filters', full_name='messages.PlayerPool.filters', index=1,
|
||||
number=2, type=11, cpp_type=10, label=3,
|
||||
has_default_value=False, default_value=[],
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='roster', full_name='messages.PlayerPool.roster', index=2,
|
||||
number=3, type=11, cpp_type=10, label=1,
|
||||
has_default_value=False, default_value=None,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='stats', full_name='messages.PlayerPool.stats', index=3,
|
||||
number=4, type=11, cpp_type=10, label=1,
|
||||
has_default_value=False, default_value=None,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=478,
|
||||
serialized_end=605,
|
||||
)
|
||||
|
||||
|
||||
_PLAYER_ATTRIBUTE = _descriptor.Descriptor(
|
||||
name='Attribute',
|
||||
full_name='messages.Player.Attribute',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='name', full_name='messages.Player.Attribute.name', index=0,
|
||||
number=1, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='value', full_name='messages.Player.Attribute.value', index=1,
|
||||
number=2, type=3, cpp_type=2, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=712,
|
||||
serialized_end=752,
|
||||
)
|
||||
|
||||
_PLAYER = _descriptor.Descriptor(
|
||||
name='Player',
|
||||
full_name='messages.Player',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='id', full_name='messages.Player.id', index=0,
|
||||
number=1, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='properties', full_name='messages.Player.properties', index=1,
|
||||
number=2, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='pool', full_name='messages.Player.pool', index=2,
|
||||
number=3, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='attributes', full_name='messages.Player.attributes', index=3,
|
||||
number=4, type=11, cpp_type=10, label=3,
|
||||
has_default_value=False, default_value=[],
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[_PLAYER_ATTRIBUTE, ],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=608,
|
||||
serialized_end=752,
|
||||
)
|
||||
|
||||
|
||||
_RESULT = _descriptor.Descriptor(
|
||||
name='Result',
|
||||
full_name='messages.Result',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='success', full_name='messages.Result.success', index=0,
|
||||
number=1, type=8, cpp_type=7, label=1,
|
||||
has_default_value=False, default_value=False,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='error', full_name='messages.Result.error', index=1,
|
||||
number=2, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=754,
|
||||
serialized_end=794,
|
||||
)
|
||||
|
||||
|
||||
_ILINPUT = _descriptor.Descriptor(
|
||||
name='IlInput',
|
||||
full_name='messages.IlInput',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=796,
|
||||
serialized_end=805,
|
||||
)
|
||||
|
||||
|
||||
_TIMESTAMP = _descriptor.Descriptor(
|
||||
name='Timestamp',
|
||||
full_name='messages.Timestamp',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='ts', full_name='messages.Timestamp.ts', index=0,
|
||||
number=1, type=3, cpp_type=2, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=807,
|
||||
serialized_end=830,
|
||||
)
|
||||
|
||||
|
||||
_CONNECTIONINFO = _descriptor.Descriptor(
|
||||
name='ConnectionInfo',
|
||||
full_name='messages.ConnectionInfo',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='connection_string', full_name='messages.ConnectionInfo.connection_string', index=0,
|
||||
number=1, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=832,
|
||||
serialized_end=875,
|
||||
)
|
||||
|
||||
|
||||
_ASSIGNMENTS = _descriptor.Descriptor(
|
||||
name='Assignments',
|
||||
full_name='messages.Assignments',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='rosters', full_name='messages.Assignments.rosters', index=0,
|
||||
number=1, type=11, cpp_type=10, label=3,
|
||||
has_default_value=False, default_value=[],
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='connection_info', full_name='messages.Assignments.connection_info', index=1,
|
||||
number=2, type=11, cpp_type=10, label=1,
|
||||
has_default_value=False, default_value=None,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
serialized_options=None, file=DESCRIPTOR),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
serialized_options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=877,
|
||||
serialized_end=976,
|
||||
)
|
||||
|
||||
_PROFILE.fields_by_name['pools'].message_type = _PLAYERPOOL
|
||||
_MATCHOBJECT.fields_by_name['rosters'].message_type = _ROSTER
|
||||
_MATCHOBJECT.fields_by_name['pools'].message_type = _PLAYERPOOL
|
||||
_ROSTER.fields_by_name['players'].message_type = _PLAYER
|
||||
_FILTER.fields_by_name['stats'].message_type = _STATS
|
||||
_PLAYERPOOL.fields_by_name['filters'].message_type = _FILTER
|
||||
_PLAYERPOOL.fields_by_name['roster'].message_type = _ROSTER
|
||||
_PLAYERPOOL.fields_by_name['stats'].message_type = _STATS
|
||||
_PLAYER_ATTRIBUTE.containing_type = _PLAYER
|
||||
_PLAYER.fields_by_name['attributes'].message_type = _PLAYER_ATTRIBUTE
|
||||
_ASSIGNMENTS.fields_by_name['rosters'].message_type = _ROSTER
|
||||
_ASSIGNMENTS.fields_by_name['connection_info'].message_type = _CONNECTIONINFO
|
||||
DESCRIPTOR.message_types_by_name['Profile'] = _PROFILE
|
||||
DESCRIPTOR.message_types_by_name['MatchObject'] = _MATCHOBJECT
|
||||
DESCRIPTOR.message_types_by_name['Roster'] = _ROSTER
|
||||
DESCRIPTOR.message_types_by_name['Filter'] = _FILTER
|
||||
DESCRIPTOR.message_types_by_name['Stats'] = _STATS
|
||||
DESCRIPTOR.message_types_by_name['PlayerPool'] = _PLAYERPOOL
|
||||
DESCRIPTOR.message_types_by_name['Player'] = _PLAYER
|
||||
DESCRIPTOR.message_types_by_name['Result'] = _RESULT
|
||||
DESCRIPTOR.message_types_by_name['IlInput'] = _ILINPUT
|
||||
DESCRIPTOR.message_types_by_name['Timestamp'] = _TIMESTAMP
|
||||
DESCRIPTOR.message_types_by_name['ConnectionInfo'] = _CONNECTIONINFO
|
||||
DESCRIPTOR.message_types_by_name['Assignments'] = _ASSIGNMENTS
|
||||
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
|
||||
|
||||
Profile = _reflection.GeneratedProtocolMessageType('Profile', (_message.Message,), dict(
|
||||
DESCRIPTOR = _PROFILE,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.Profile)
|
||||
))
|
||||
_sym_db.RegisterMessage(Profile)
|
||||
|
||||
MatchObject = _reflection.GeneratedProtocolMessageType('MatchObject', (_message.Message,), dict(
|
||||
DESCRIPTOR = _MATCHOBJECT,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.MatchObject)
|
||||
))
|
||||
_sym_db.RegisterMessage(MatchObject)
|
||||
|
||||
Roster = _reflection.GeneratedProtocolMessageType('Roster', (_message.Message,), dict(
|
||||
DESCRIPTOR = _ROSTER,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.Roster)
|
||||
))
|
||||
_sym_db.RegisterMessage(Roster)
|
||||
|
||||
Filter = _reflection.GeneratedProtocolMessageType('Filter', (_message.Message,), dict(
|
||||
DESCRIPTOR = _FILTER,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.Filter)
|
||||
))
|
||||
_sym_db.RegisterMessage(Filter)
|
||||
|
||||
Stats = _reflection.GeneratedProtocolMessageType('Stats', (_message.Message,), dict(
|
||||
DESCRIPTOR = _STATS,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.Stats)
|
||||
))
|
||||
_sym_db.RegisterMessage(Stats)
|
||||
|
||||
PlayerPool = _reflection.GeneratedProtocolMessageType('PlayerPool', (_message.Message,), dict(
|
||||
DESCRIPTOR = _PLAYERPOOL,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.PlayerPool)
|
||||
))
|
||||
_sym_db.RegisterMessage(PlayerPool)
|
||||
|
||||
Player = _reflection.GeneratedProtocolMessageType('Player', (_message.Message,), dict(
|
||||
|
||||
Attribute = _reflection.GeneratedProtocolMessageType('Attribute', (_message.Message,), dict(
|
||||
DESCRIPTOR = _PLAYER_ATTRIBUTE,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.Player.Attribute)
|
||||
))
|
||||
,
|
||||
DESCRIPTOR = _PLAYER,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.Player)
|
||||
))
|
||||
_sym_db.RegisterMessage(Player)
|
||||
_sym_db.RegisterMessage(Player.Attribute)
|
||||
|
||||
Result = _reflection.GeneratedProtocolMessageType('Result', (_message.Message,), dict(
|
||||
DESCRIPTOR = _RESULT,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.Result)
|
||||
))
|
||||
_sym_db.RegisterMessage(Result)
|
||||
|
||||
IlInput = _reflection.GeneratedProtocolMessageType('IlInput', (_message.Message,), dict(
|
||||
DESCRIPTOR = _ILINPUT,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.IlInput)
|
||||
))
|
||||
_sym_db.RegisterMessage(IlInput)
|
||||
|
||||
Timestamp = _reflection.GeneratedProtocolMessageType('Timestamp', (_message.Message,), dict(
|
||||
DESCRIPTOR = _TIMESTAMP,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.Timestamp)
|
||||
))
|
||||
_sym_db.RegisterMessage(Timestamp)
|
||||
|
||||
ConnectionInfo = _reflection.GeneratedProtocolMessageType('ConnectionInfo', (_message.Message,), dict(
|
||||
DESCRIPTOR = _CONNECTIONINFO,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.ConnectionInfo)
|
||||
))
|
||||
_sym_db.RegisterMessage(ConnectionInfo)
|
||||
|
||||
Assignments = _reflection.GeneratedProtocolMessageType('Assignments', (_message.Message,), dict(
|
||||
DESCRIPTOR = _ASSIGNMENTS,
|
||||
__module__ = 'api.protobuf_spec.messages_pb2'
|
||||
# @@protoc_insertion_point(class_scope:messages.Assignments)
|
||||
))
|
||||
_sym_db.RegisterMessage(Assignments)
|
||||
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
# @@protoc_insertion_point(module_scope)
|
@ -0,0 +1,16 @@
|
||||
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
||||
#Copyright 2018 Google LLC
|
||||
#Licensed under the Apache License, Version 2.0 (the "License");
|
||||
#you may not use this file except in compliance with the License.
|
||||
#You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
#Unless required by applicable law or agreed to in writing, software
|
||||
#distributed under the License is distributed on an "AS IS" BASIS,
|
||||
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#See the License for the specific language governing permissions and
|
||||
#limitations under the License.
|
||||
|
||||
import grpc
|
||||
|
@ -0,0 +1,114 @@
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: api/protobuf-spec/mmlogic.proto
|
||||
#Copyright 2018 Google LLC
|
||||
#Licensed under the Apache License, Version 2.0 (the "License");
|
||||
#you may not use this file except in compliance with the License.
|
||||
#You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
#Unless required by applicable law or agreed to in writing, software
|
||||
#distributed under the License is distributed on an "AS IS" BASIS,
|
||||
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#See the License for the specific language governing permissions and
|
||||
#limitations under the License.
|
||||
|
||||
import sys
|
||||
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from api.protobuf_spec import messages_pb2 as api_dot_protobuf__spec_dot_messages__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor.FileDescriptor(
|
||||
name='api/protobuf-spec/mmlogic.proto',
|
||||
package='api',
|
||||
syntax='proto3',
|
||||
serialized_options=_b('Z5github.com/GoogleCloudPlatform/open-match/internal/pb'),
|
||||
serialized_pb=_b('\n\x1f\x61pi/protobuf-spec/mmlogic.proto\x12\x03\x61pi\x1a api/protobuf-spec/messages.proto2\xf6\x02\n\x07MmLogic\x12<\n\nGetProfile\x12\x15.messages.MatchObject\x1a\x15.messages.MatchObject\"\x00\x12;\n\x0e\x43reateProposal\x12\x15.messages.MatchObject\x1a\x10.messages.Result\"\x00\x12\x33\n\x0bReturnError\x12\x10.messages.Result\x1a\x10.messages.Result\"\x00\x12?\n\rGetPlayerPool\x12\x14.messages.PlayerPool\x1a\x14.messages.PlayerPool\"\x00\x30\x01\x12=\n\x14GetAllIgnoredPlayers\x12\x11.messages.IlInput\x1a\x10.messages.Roster\"\x00\x12;\n\x12ListIgnoredPlayers\x12\x11.messages.IlInput\x1a\x10.messages.Roster\"\x00\x42\x37Z5github.com/GoogleCloudPlatform/open-match/internal/pbb\x06proto3')
|
||||
,
|
||||
dependencies=[api_dot_protobuf__spec_dot_messages__pb2.DESCRIPTOR,])
|
||||
|
||||
|
||||
|
||||
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
|
||||
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
|
||||
_MMLOGIC = _descriptor.ServiceDescriptor(
|
||||
name='MmLogic',
|
||||
full_name='api.MmLogic',
|
||||
file=DESCRIPTOR,
|
||||
index=0,
|
||||
serialized_options=None,
|
||||
serialized_start=75,
|
||||
serialized_end=449,
|
||||
methods=[
|
||||
_descriptor.MethodDescriptor(
|
||||
name='GetProfile',
|
||||
full_name='api.MmLogic.GetProfile',
|
||||
index=0,
|
||||
containing_service=None,
|
||||
input_type=api_dot_protobuf__spec_dot_messages__pb2._MATCHOBJECT,
|
||||
output_type=api_dot_protobuf__spec_dot_messages__pb2._MATCHOBJECT,
|
||||
serialized_options=None,
|
||||
),
|
||||
_descriptor.MethodDescriptor(
|
||||
name='CreateProposal',
|
||||
full_name='api.MmLogic.CreateProposal',
|
||||
index=1,
|
||||
containing_service=None,
|
||||
input_type=api_dot_protobuf__spec_dot_messages__pb2._MATCHOBJECT,
|
||||
output_type=api_dot_protobuf__spec_dot_messages__pb2._RESULT,
|
||||
serialized_options=None,
|
||||
),
|
||||
_descriptor.MethodDescriptor(
|
||||
name='ReturnError',
|
||||
full_name='api.MmLogic.ReturnError',
|
||||
index=2,
|
||||
containing_service=None,
|
||||
input_type=api_dot_protobuf__spec_dot_messages__pb2._RESULT,
|
||||
output_type=api_dot_protobuf__spec_dot_messages__pb2._RESULT,
|
||||
serialized_options=None,
|
||||
),
|
||||
_descriptor.MethodDescriptor(
|
||||
name='GetPlayerPool',
|
||||
full_name='api.MmLogic.GetPlayerPool',
|
||||
index=3,
|
||||
containing_service=None,
|
||||
input_type=api_dot_protobuf__spec_dot_messages__pb2._PLAYERPOOL,
|
||||
output_type=api_dot_protobuf__spec_dot_messages__pb2._PLAYERPOOL,
|
||||
serialized_options=None,
|
||||
),
|
||||
_descriptor.MethodDescriptor(
|
||||
name='GetAllIgnoredPlayers',
|
||||
full_name='api.MmLogic.GetAllIgnoredPlayers',
|
||||
index=4,
|
||||
containing_service=None,
|
||||
input_type=api_dot_protobuf__spec_dot_messages__pb2._ILINPUT,
|
||||
output_type=api_dot_protobuf__spec_dot_messages__pb2._ROSTER,
|
||||
serialized_options=None,
|
||||
),
|
||||
_descriptor.MethodDescriptor(
|
||||
name='ListIgnoredPlayers',
|
||||
full_name='api.MmLogic.ListIgnoredPlayers',
|
||||
index=5,
|
||||
containing_service=None,
|
||||
input_type=api_dot_protobuf__spec_dot_messages__pb2._ILINPUT,
|
||||
output_type=api_dot_protobuf__spec_dot_messages__pb2._ROSTER,
|
||||
serialized_options=None,
|
||||
),
|
||||
])
|
||||
_sym_db.RegisterServiceDescriptor(_MMLOGIC)
|
||||
|
||||
DESCRIPTOR.services_by_name['MmLogic'] = _MMLOGIC
|
||||
|
||||
# @@protoc_insertion_point(module_scope)
|
@ -0,0 +1,160 @@
|
||||
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
||||
#Copyright 2018 Google LLC
|
||||
#Licensed under the Apache License, Version 2.0 (the "License");
|
||||
#you may not use this file except in compliance with the License.
|
||||
#You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
#Unless required by applicable law or agreed to in writing, software
|
||||
#distributed under the License is distributed on an "AS IS" BASIS,
|
||||
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#See the License for the specific language governing permissions and
|
||||
#limitations under the License.
|
||||
import grpc
|
||||
|
||||
from api.protobuf_spec import messages_pb2 as api_dot_protobuf__spec_dot_messages__pb2
|
||||
|
||||
|
||||
class MmLogicStub(object):
|
||||
"""Profile and match object functions
|
||||
If your matchmaking logic makes a match, it should CreateProposal. If it
|
||||
cannot, it should ReturnError.
|
||||
"""
|
||||
|
||||
def __init__(self, channel):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
channel: A grpc.Channel.
|
||||
"""
|
||||
self.GetProfile = channel.unary_unary(
|
||||
'/api.MmLogic/GetProfile',
|
||||
request_serializer=api_dot_protobuf__spec_dot_messages__pb2.MatchObject.SerializeToString,
|
||||
response_deserializer=api_dot_protobuf__spec_dot_messages__pb2.MatchObject.FromString,
|
||||
)
|
||||
self.CreateProposal = channel.unary_unary(
|
||||
'/api.MmLogic/CreateProposal',
|
||||
request_serializer=api_dot_protobuf__spec_dot_messages__pb2.MatchObject.SerializeToString,
|
||||
response_deserializer=api_dot_protobuf__spec_dot_messages__pb2.Result.FromString,
|
||||
)
|
||||
self.ReturnError = channel.unary_unary(
|
||||
'/api.MmLogic/ReturnError',
|
||||
request_serializer=api_dot_protobuf__spec_dot_messages__pb2.Result.SerializeToString,
|
||||
response_deserializer=api_dot_protobuf__spec_dot_messages__pb2.Result.FromString,
|
||||
)
|
||||
self.GetPlayerPool = channel.unary_stream(
|
||||
'/api.MmLogic/GetPlayerPool',
|
||||
request_serializer=api_dot_protobuf__spec_dot_messages__pb2.PlayerPool.SerializeToString,
|
||||
response_deserializer=api_dot_protobuf__spec_dot_messages__pb2.PlayerPool.FromString,
|
||||
)
|
||||
self.GetAllIgnoredPlayers = channel.unary_unary(
|
||||
'/api.MmLogic/GetAllIgnoredPlayers',
|
||||
request_serializer=api_dot_protobuf__spec_dot_messages__pb2.IlInput.SerializeToString,
|
||||
response_deserializer=api_dot_protobuf__spec_dot_messages__pb2.Roster.FromString,
|
||||
)
|
||||
self.ListIgnoredPlayers = channel.unary_unary(
|
||||
'/api.MmLogic/ListIgnoredPlayers',
|
||||
request_serializer=api_dot_protobuf__spec_dot_messages__pb2.IlInput.SerializeToString,
|
||||
response_deserializer=api_dot_protobuf__spec_dot_messages__pb2.Roster.FromString,
|
||||
)
|
||||
|
||||
|
||||
class MmLogicServicer(object):
|
||||
"""Profile and match object functions
|
||||
If your matchmaking logic makes a match, it should CreateProposal. If it
|
||||
cannot, it should ReturnError.
|
||||
"""
|
||||
|
||||
def GetProfile(self, request, context):
|
||||
"""Send GetProfile a match object with the ID field populated, it will return a
|
||||
'filled' one.
|
||||
Note: filters are assumed to have been checked for validity by the
|
||||
backendapi when accepting a profile
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def CreateProposal(self, request, context):
|
||||
"""CreateProposal does the following:
|
||||
- adds all players in the Roster to the proposed player ignore list
|
||||
- writes the proposed match to the provided key
|
||||
- adds that key to the list of proposals to be considered
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def ReturnError(self, request, context):
|
||||
# missing associated documentation comment in .proto file
|
||||
pass
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetPlayerPool(self, request, context):
|
||||
"""Player listing and filtering functions
|
||||
|
||||
RetrievePlayerPool gets the list of players for every Filter in the
|
||||
PlayerPool, and then removes all players it finds in the ignore list. It
|
||||
combines the results, and returns the resulting player pool.
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetAllIgnoredPlayers(self, request, context):
|
||||
"""Ignore List functions
|
||||
|
||||
IlInput is an empty message reserved for future use.
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def ListIgnoredPlayers(self, request, context):
|
||||
"""RetrieveIgnoreList retrieves players from the ignore list specified in the
|
||||
config file under 'ignoreLists.proposedPlayers.key'.
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
|
||||
def add_MmLogicServicer_to_server(servicer, server):
|
||||
rpc_method_handlers = {
|
||||
'GetProfile': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.GetProfile,
|
||||
request_deserializer=api_dot_protobuf__spec_dot_messages__pb2.MatchObject.FromString,
|
||||
response_serializer=api_dot_protobuf__spec_dot_messages__pb2.MatchObject.SerializeToString,
|
||||
),
|
||||
'CreateProposal': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.CreateProposal,
|
||||
request_deserializer=api_dot_protobuf__spec_dot_messages__pb2.MatchObject.FromString,
|
||||
response_serializer=api_dot_protobuf__spec_dot_messages__pb2.Result.SerializeToString,
|
||||
),
|
||||
'ReturnError': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.ReturnError,
|
||||
request_deserializer=api_dot_protobuf__spec_dot_messages__pb2.Result.FromString,
|
||||
response_serializer=api_dot_protobuf__spec_dot_messages__pb2.Result.SerializeToString,
|
||||
),
|
||||
'GetPlayerPool': grpc.unary_stream_rpc_method_handler(
|
||||
servicer.GetPlayerPool,
|
||||
request_deserializer=api_dot_protobuf__spec_dot_messages__pb2.PlayerPool.FromString,
|
||||
response_serializer=api_dot_protobuf__spec_dot_messages__pb2.PlayerPool.SerializeToString,
|
||||
),
|
||||
'GetAllIgnoredPlayers': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.GetAllIgnoredPlayers,
|
||||
request_deserializer=api_dot_protobuf__spec_dot_messages__pb2.IlInput.FromString,
|
||||
response_serializer=api_dot_protobuf__spec_dot_messages__pb2.Roster.SerializeToString,
|
||||
),
|
||||
'ListIgnoredPlayers': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.ListIgnoredPlayers,
|
||||
request_deserializer=api_dot_protobuf__spec_dot_messages__pb2.IlInput.FromString,
|
||||
response_serializer=api_dot_protobuf__spec_dot_messages__pb2.Roster.SerializeToString,
|
||||
),
|
||||
}
|
||||
generic_handler = grpc.method_handlers_generic_handler(
|
||||
'api.MmLogic', rpc_method_handlers)
|
||||
server.add_generic_rpc_handlers((generic_handler,))
|
119
examples/functions/python3/mmlogic-simple/harness.py
Normal file
119
examples/functions/python3/mmlogic-simple/harness.py
Normal file
@ -0,0 +1,119 @@
|
||||
#! /usr/bin/env python3
|
||||
#Copyright 2018 Google LLC
|
||||
#Licensed under the Apache License, Version 2.0 (the "License");
|
||||
#you may not use this file except in compliance with the License.
|
||||
#You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
#Unless required by applicable law or agreed to in writing, software
|
||||
#distributed under the License is distributed on an "AS IS" BASIS,
|
||||
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#See the License for the specific language governing permissions and
|
||||
#limitations under the License.
|
||||
# Note:
|
||||
# This harness exits with a success code even in cases of error as
|
||||
# kubernetes jobs will re-run until it sees a successful exit code.
|
||||
# Errors are populated through the backend API back to the backend API client.
|
||||
import os
|
||||
import grpc
|
||||
import mmf
|
||||
import api.protobuf_spec.messages_pb2 as mmlogic
|
||||
import api.protobuf_spec.mmlogic_pb2_grpc as mmlogic_pb2_grpc
|
||||
from google.protobuf.json_format import Parse
|
||||
import ujson as json
|
||||
import pprint as pp
|
||||
|
||||
from timeit import default_timer as timer
|
||||
|
||||
# Load config file
|
||||
cfg = ''
|
||||
with open("matchmaker_config.json") as f:
|
||||
cfg = json.loads(f.read())
|
||||
|
||||
# Step 2 - Talk to Redis. This example uses the MM Logic API in OM to read/write to/from redis.
|
||||
# Establish grpc channel and make the API client stub
|
||||
api_conn_info = "%s:%d" % (cfg['api']['mmlogic']['hostname'], cfg['api']['mmlogic']['port'])
|
||||
with grpc.insecure_channel(api_conn_info) as channel:
|
||||
mmlogic_api = mmlogic_pb2_grpc.MmLogicStub(channel)
|
||||
|
||||
# Step 3 - Read the profile written to the Backend API.
|
||||
# Get profile from redis
|
||||
profile_pb = mmlogic_api.GetProfile(mmlogic.MatchObject(id=os.environ["MMF_PROFILE_ID"]))
|
||||
pp.pprint(profile_pb) #DEBUG
|
||||
profile_dict = json.loads(profile_pb.properties)
|
||||
|
||||
# Step 4 - Select the player data from Redis that we want for our matchmaking logic.
|
||||
# Embedded in this profile are JSON representations of the filters for each player pool.
|
||||
# JsonFilterSet() is able to read those directly. No need to marhal that
|
||||
# JSON into the protobuf message format.
|
||||
player_pools = dict() # holds dictionary version of the pools.
|
||||
for empty_pool in profile_pb.pools:
|
||||
# Dict to hold value-sorted field dictionary for easy retreival of players by value
|
||||
player_pools[empty_pool.name] = dict()
|
||||
print("Retrieving pool '%s'" % empty_pool.name, end='')
|
||||
|
||||
# DEBUG: Print how long the filtering takes
|
||||
if cfg['debug']:
|
||||
start = timer()
|
||||
|
||||
# Pool filter results are streamed in chunks as they can be too large to send
|
||||
# in one grpc message. Loop to get them all.
|
||||
for partial_results in mmlogic_api.GetPlayerPool(empty_pool):
|
||||
empty_pool.stats.count = partial_results.stats.count
|
||||
empty_pool.stats.elapsed = partial_results.stats.elapsed
|
||||
print(".", end='')
|
||||
try:
|
||||
for player in partial_results.roster.players:
|
||||
if not player.id in player_pools[empty_pool.name]:
|
||||
player_pools[empty_pool.name][player.id] = dict()
|
||||
for attr in player.attributes:
|
||||
player_pools[empty_pool.name][player.id][attr.name] = attr.value
|
||||
except Exception as err:
|
||||
print("Error encountered: %s" % err)
|
||||
if cfg['debug']:
|
||||
print("\n'%s': count %06d | elapsed %0.3f" % (empty_pool.name, len(player_pools[empty_pool.name]),timer() - start))
|
||||
|
||||
#################################################################
|
||||
# Step 5 - Run custom matchmaking logic to try to find a match
|
||||
# This is in the file mmf.py
|
||||
results = mmf.makeMatches(profile_dict, player_pools)
|
||||
#################################################################
|
||||
|
||||
# DEBUG
|
||||
if cfg['debug']:
|
||||
print("======== match_properties")
|
||||
pp.pprint(results)
|
||||
|
||||
# Generate a MatchObject message to write to state storage with the results in it.
|
||||
# This looks odd but it's how you assign to repeated protobuf fields.
|
||||
# https://developers.google.com/protocol-buffers/docs/reference/python-generated#repeated-fields
|
||||
match_properties = json.dumps(results)
|
||||
mo = mmlogic.MatchObject(id = os.environ["MMF_PROPOSAL_ID"], properties = match_properties)
|
||||
mo.pools.extend(profile_pb.pools[:])
|
||||
|
||||
# Access the rosters in dict form within the properties json.
|
||||
# It is stored at the key specified in the config file.
|
||||
rosters_dict = results
|
||||
for partial_key in cfg['jsonkeys']['rosters'].split('.'):
|
||||
rosters_dict = rosters_dict.get(partial_key, {})
|
||||
|
||||
# Unmarshal the rosters into the MatchObject
|
||||
for roster in rosters_dict:
|
||||
mo.rosters.extend([Parse(json.dumps(roster), mmlogic.Roster(), ignore_unknown_fields=True)])
|
||||
|
||||
#DEBUG: writing to error key prevents evalutor run
|
||||
if cfg['debug']:
|
||||
#mo.id = os.environ["MMF_ERROR_ID"]
|
||||
#mo.error = "skip evaluator"
|
||||
print("======== MMF results:")
|
||||
pp.pprint(mo)
|
||||
|
||||
# Step 6 - Write the outcome of the matchmaking logic back to state storage.
|
||||
# Step 7 - Remove the selected players from consideration by other MMFs.
|
||||
# CreateProposal does both of these for you, and some other items as well.
|
||||
success = mmlogic_api.CreateProposal(mo)
|
||||
print("======== MMF write to state storage: %s" % success)
|
||||
|
||||
# [OPTIONAL] Step 8 - Export stats about this run.
|
||||
# TODO
|
1
examples/functions/python3/mmlogic-simple/matchmaker_config.json
Symbolic link
1
examples/functions/python3/mmlogic-simple/matchmaker_config.json
Symbolic link
@ -0,0 +1 @@
|
||||
../../../../config/matchmaker_config.json
|
38
examples/functions/python3/mmlogic-simple/mmf.py
Normal file
38
examples/functions/python3/mmlogic-simple/mmf.py
Normal file
@ -0,0 +1,38 @@
|
||||
#! /usr/bin/env python3
|
||||
#Copyright 2018 Google LLC
|
||||
#Licensed under the Apache License, Version 2.0 (the "License");
|
||||
#you may not use this file except in compliance with the License.
|
||||
#You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
#Unless required by applicable law or agreed to in writing, software
|
||||
#distributed under the License is distributed on an "AS IS" BASIS,
|
||||
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#See the License for the specific language governing permissions and
|
||||
#limitations under the License.
|
||||
|
||||
import random
|
||||
|
||||
def makeMatches(profile_dict, player_pools):
|
||||
###########################################################################
|
||||
# This is the exciting part, and where most of your custom code would go! #
|
||||
###########################################################################
|
||||
|
||||
# The python3 MMF harness passed this function filtered players and their
|
||||
# filtered attributes in the player_pools dictionary. If we wanted to evaluate
|
||||
# other player attributes, we could connect to redis directly and query the
|
||||
# players by their ID to get the entire 'properties' player JSON passed in
|
||||
# to the frontend API when they entered matchmaking.
|
||||
|
||||
# This basic example just pulls players at random from the specified pools in the
|
||||
# profile. This just serves to show how the dictionaries are accessed and you
|
||||
# should write your own rigourous logic here.
|
||||
for roster in profile_dict['properties']['rosters']:
|
||||
for player in roster['players']:
|
||||
if 'pool' in player:
|
||||
player['id'] = random.choice(list(player_pools[player['pool']]))
|
||||
print("Selected player %s from pool %s (strategy: RANDOM)" % (player['id'], player['pool']))
|
||||
else:
|
||||
print(player)
|
||||
return profile_dict
|
@ -0,0 +1,6 @@
|
||||
googleapis-common-protos==1.5.3
|
||||
grpcio==1.15.0
|
||||
grpcio-tools==1.15.0
|
||||
protobuf==3.6.1
|
||||
ujson==1.35
|
||||
six==1.11.0
|
36
install/yaml/01-redis.yaml
Normal file
36
install/yaml/01-redis.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: redis-sentinel
|
||||
spec:
|
||||
selector:
|
||||
app: mm
|
||||
tier: storage
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 6379
|
||||
targetPort: redis
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis-master
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mm
|
||||
tier: storage
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: mm
|
||||
tier: storage
|
||||
spec:
|
||||
containers:
|
||||
- name: redis-master
|
||||
image: redis:4.0.11
|
||||
ports:
|
||||
- name: redis
|
||||
containerPort: 6379
|
190
install/yaml/02-open-match.yaml
Normal file
190
install/yaml/02-open-match.yaml
Normal file
@ -0,0 +1,190 @@
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: om-backendapi
|
||||
spec:
|
||||
selector:
|
||||
app: openmatch
|
||||
component: backend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 50505
|
||||
targetPort: grpc
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: om-backendapi
|
||||
labels:
|
||||
app: openmatch
|
||||
component: backend
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: openmatch
|
||||
component: backend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: openmatch
|
||||
component: backend
|
||||
spec:
|
||||
containers:
|
||||
- name: om-backend
|
||||
image: gcr.io/matchmaker-dev-201405/openmatch-backendapi:dev
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: grpc
|
||||
containerPort: 50505
|
||||
- name: metrics
|
||||
containerPort: 9555
|
||||
resources:
|
||||
requests:
|
||||
memory: 100Mi
|
||||
cpu: 100m
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: om-frontendapi
|
||||
spec:
|
||||
selector:
|
||||
app: openmatch
|
||||
component: frontend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 50504
|
||||
targetPort: grpc
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: om-frontendapi
|
||||
labels:
|
||||
app: openmatch
|
||||
component: frontend
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: openmatch
|
||||
component: frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: openmatch
|
||||
component: frontend
|
||||
spec:
|
||||
containers:
|
||||
- name: om-frontendapi
|
||||
image: gcr.io/matchmaker-dev-201405/openmatch-frontendapi:dev
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: grpc
|
||||
containerPort: 50504
|
||||
- name: metrics
|
||||
containerPort: 9555
|
||||
resources:
|
||||
requests:
|
||||
memory: 100Mi
|
||||
cpu: 100m
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: mmf-sa
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: default
|
||||
namespace: default
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: cluster-admin
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: om-mmforc
|
||||
labels:
|
||||
app: openmatch
|
||||
component: mmforc
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: openmatch
|
||||
component: mmforc
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: openmatch
|
||||
component: mmforc
|
||||
spec:
|
||||
containers:
|
||||
- name: om-mmforc
|
||||
image: gcr.io/matchmaker-dev-201405/openmatch-mmforc:dev
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: metrics
|
||||
containerPort: 9555
|
||||
resources:
|
||||
requests:
|
||||
memory: 100Mi
|
||||
cpu: 100m
|
||||
env:
|
||||
- name: METADATA_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: om-mmlogicapi
|
||||
labels:
|
||||
app: openmatch
|
||||
component: mmlogic
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: openmatch
|
||||
component: mmlogic
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: openmatch
|
||||
component: mmlogic
|
||||
spec:
|
||||
containers:
|
||||
- name: om-mmlogic
|
||||
image: gcr.io/matchmaker-dev-201405/openmatch-mmlogicapi:dev
|
||||
imagePullPolicy: Always
|
||||
command:
|
||||
- sleep
|
||||
- '30000'
|
||||
ports:
|
||||
- name: grpc
|
||||
containerPort: 50503
|
||||
- name: metrics
|
||||
containerPort: 9555
|
||||
resources:
|
||||
requests:
|
||||
memory: 100Mi
|
||||
cpu: 100m
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: om-mmlogicapi
|
||||
spec:
|
||||
selector:
|
||||
app: openmatch
|
||||
component: mmlogic
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 50503
|
||||
targetPort: grpc
|
266
install/yaml/03-prometheus.yaml
Normal file
266
install/yaml/03-prometheus.yaml
Normal file
@ -0,0 +1,266 @@
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: openmatch-metrics
|
||||
labels:
|
||||
app: openmatch
|
||||
agent: opencensus
|
||||
destination: prometheus
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: openmatch
|
||||
agent: opencensus
|
||||
destination: prometheus
|
||||
endpoints:
|
||||
- port: metrics
|
||||
interval: 10s
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: om-frontend-metrics
|
||||
labels:
|
||||
app: openmatch
|
||||
component: frontend
|
||||
agent: opencensus
|
||||
destination: prometheus
|
||||
spec:
|
||||
selector:
|
||||
app: openmatch
|
||||
component: frontend
|
||||
ports:
|
||||
- name: metrics
|
||||
targetPort: 9555
|
||||
port: 19555
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: om-backend-metrics
|
||||
labels:
|
||||
app: openmatch
|
||||
component: backend
|
||||
agent: opencensus
|
||||
destination: prometheus
|
||||
spec:
|
||||
selector:
|
||||
app: openmatch
|
||||
component: backend
|
||||
ports:
|
||||
- name: metrics
|
||||
targetPort: 9555
|
||||
port: 29555
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: om-mmforc-metrics
|
||||
labels:
|
||||
app: openmatch
|
||||
component: mmforc
|
||||
agent: opencensus
|
||||
destination: prometheus
|
||||
spec:
|
||||
selector:
|
||||
app: openmatch
|
||||
component: mmforc
|
||||
ports:
|
||||
- name: metrics
|
||||
targetPort: 9555
|
||||
port: 39555
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: prometheus-operator
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: prometheus-operator
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: prometheus-operator
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: prometheus
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: prometheus
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- nodes
|
||||
- services
|
||||
- endpoints
|
||||
- pods
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- configmaps
|
||||
verbs:
|
||||
- get
|
||||
- nonResourceURLs:
|
||||
- "/metrics"
|
||||
verbs:
|
||||
- get
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: prometheus
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: prometheus
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: prometheus
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: prometheus-operator
|
||||
rules:
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- thirdpartyresources
|
||||
verbs:
|
||||
- "*"
|
||||
- apiGroups:
|
||||
- apiextensions.k8s.io
|
||||
resources:
|
||||
- customresourcedefinitions
|
||||
verbs:
|
||||
- "*"
|
||||
- apiGroups:
|
||||
- monitoring.coreos.com
|
||||
resources:
|
||||
- alertmanagers
|
||||
- prometheuses
|
||||
- prometheuses/finalizers
|
||||
- servicemonitors
|
||||
verbs:
|
||||
- "*"
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- statefulsets
|
||||
verbs:
|
||||
- "*"
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- configmaps
|
||||
- secrets
|
||||
verbs:
|
||||
- "*"
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- list
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- services
|
||||
- endpoints
|
||||
verbs:
|
||||
- get
|
||||
- create
|
||||
- update
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- nodes
|
||||
verbs:
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- namespaces
|
||||
verbs:
|
||||
- list
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: prometheus-operator
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: prometheus-operator
|
||||
name: prometheus-operator
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: prometheus-operator
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- "--kubelet-service=kube-system/kubelet"
|
||||
- "--config-reloader-image=quay.io/coreos/configmap-reload:v0.0.1"
|
||||
image: quay.io/coreos/prometheus-operator:v0.17.0
|
||||
name: prometheus-operator
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
resources:
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 100Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65534
|
||||
serviceAccountName: prometheus-operator
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: prometheus
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- name: web
|
||||
nodePort: 30900
|
||||
port: 9090
|
||||
protocol: TCP
|
||||
targetPort: web
|
||||
selector:
|
||||
prometheus: prometheus
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: Prometheus
|
||||
metadata:
|
||||
name: prometheus
|
||||
spec:
|
||||
serviceMonitorSelector:
|
||||
matchLabels:
|
||||
app: openmatch
|
||||
serviceAccountName: prometheus
|
||||
resources:
|
||||
requests:
|
||||
memory: 400Mi
|
73
install/yaml/README.md
Normal file
73
install/yaml/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
# install/yaml
|
||||
This directory contains Kubernetes YAML resource definitions, which should be applied according to their filename order. Only Redis & Open Match are required, Prometheus is optional.
|
||||
```
|
||||
kubectl apply -f 01-redis.yaml
|
||||
kubectl apply -f 02-open-match.yaml
|
||||
```
|
||||
**Note**: Trying to apply the Kubernetes Prometheus Operator resource definition files without a cluster-admin rolebinding on GKE doesn't work without running the following command first. See https://github.com/coreos/prometheus-operator/issues/357
|
||||
```
|
||||
kubectl create clusterrolebinding projectowner-cluster-admin-binding --clusterrole=cluster-admin --user=<GCP_ACCOUNT>
|
||||
```
|
||||
```
|
||||
kubectl apply -f 03-prometheus.yaml
|
||||
```
|
||||
[There is a known dependency ordering issue when applying the Prometheus resource; just wait a couple moments and apply it again.](https://github.com/GoogleCloudPlatform/open-match/issues/46)
|
||||
|
||||
[Accurate as of v0.2.0] Output from `kubectl get all` if everything succeeded should look something like this:
|
||||
```
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
pod/om-backendapi-84bc9d8fff-q89kr 1/1 Running 0 9m
|
||||
pod/om-frontendapi-55d5bb7946-c5ccb 1/1 Running 0 9m
|
||||
pod/om-mmforc-85bfd7f4f6-wmwhc 1/1 Running 0 9m
|
||||
pod/om-mmlogicapi-6488bc7fc6-g74dm 1/1 Running 0 9m
|
||||
pod/prometheus-operator-5c8774cdd8-7c5qm 1/1 Running 0 9m
|
||||
pod/prometheus-prometheus-0 2/2 Running 0 9m
|
||||
pod/redis-master-9b6b86c46-b7ggn 1/1 Running 0 9m
|
||||
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
service/kubernetes ClusterIP 10.59.240.1 <none> 443/TCP 19m
|
||||
service/om-backend-metrics ClusterIP 10.59.254.43 <none> 29555/TCP 9m
|
||||
service/om-backendapi ClusterIP 10.59.240.211 <none> 50505/TCP 9m
|
||||
service/om-frontend-metrics ClusterIP 10.59.246.228 <none> 19555/TCP 9m
|
||||
service/om-frontendapi ClusterIP 10.59.250.59 <none> 50504/TCP 9m
|
||||
service/om-mmforc-metrics ClusterIP 10.59.240.59 <none> 39555/TCP 9m
|
||||
service/om-mmlogicapi ClusterIP 10.59.248.3 <none> 50503/TCP 9m
|
||||
service/prometheus NodePort 10.59.252.212 <none> 9090:30900/TCP 9m
|
||||
service/prometheus-operated ClusterIP None <none> 9090/TCP 9m
|
||||
service/redis-sentinel ClusterIP 10.59.249.197 <none> 6379/TCP 9m
|
||||
|
||||
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
|
||||
deployment.extensions/om-backendapi 1 1 1 1 9m
|
||||
deployment.extensions/om-frontendapi 1 1 1 1 9m
|
||||
deployment.extensions/om-mmforc 1 1 1 1 9m
|
||||
deployment.extensions/om-mmlogicapi 1 1 1 1 9m
|
||||
deployment.extensions/prometheus-operator 1 1 1 1 9m
|
||||
deployment.extensions/redis-master 1 1 1 1 9m
|
||||
|
||||
NAME DESIRED CURRENT READY AGE
|
||||
replicaset.extensions/om-backendapi-84bc9d8fff 1 1 1 9m
|
||||
replicaset.extensions/om-frontendapi-55d5bb7946 1 1 1 9m
|
||||
replicaset.extensions/om-mmforc-85bfd7f4f6 1 1 1 9m
|
||||
replicaset.extensions/om-mmlogicapi-6488bc7fc6 1 1 1 9m
|
||||
replicaset.extensions/prometheus-operator-5c8774cdd8 1 1 1 9m
|
||||
replicaset.extensions/redis-master-9b6b86c46 1 1 1 9m
|
||||
|
||||
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
|
||||
deployment.apps/om-backendapi 1 1 1 1 9m
|
||||
deployment.apps/om-frontendapi 1 1 1 1 9m
|
||||
deployment.apps/om-mmforc 1 1 1 1 9m
|
||||
deployment.apps/om-mmlogicapi 1 1 1 1 9m
|
||||
deployment.apps/prometheus-operator 1 1 1 1 9m
|
||||
deployment.apps/redis-master 1 1 1 1 9m
|
||||
|
||||
NAME DESIRED CURRENT READY AGE
|
||||
replicaset.apps/om-backendapi-84bc9d8fff 1 1 1 9m
|
||||
replicaset.apps/om-frontendapi-55d5bb7946 1 1 1 9m
|
||||
replicaset.apps/om-mmforc-85bfd7f4f6 1 1 1 9m
|
||||
replicaset.apps/om-mmlogicapi-6488bc7fc6 1 1 1 9m
|
||||
replicaset.apps/prometheus-operator-5c8774cdd8 1 1 1 9m
|
||||
replicaset.apps/redis-master-9b6b86c46 1 1 1 9m
|
||||
|
||||
NAME DESIRED CURRENT AGE
|
||||
statefulset.apps/prometheus-prometheus 1 1 9m
|
||||
```
|
372
internal/pb/backend.pb.go
Normal file
372
internal/pb/backend.pb.go
Normal file
@ -0,0 +1,372 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: api/protobuf-spec/backend.proto
|
||||
|
||||
/*
|
||||
Package pb is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
api/protobuf-spec/backend.proto
|
||||
api/protobuf-spec/frontend.proto
|
||||
api/protobuf-spec/mmlogic.proto
|
||||
api/protobuf-spec/messages.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Group
|
||||
PlayerId
|
||||
MatchObject
|
||||
Roster
|
||||
Filter
|
||||
Stats
|
||||
PlayerPool
|
||||
Player
|
||||
Result
|
||||
IlInput
|
||||
ConnectionInfo
|
||||
Assignments
|
||||
*/
|
||||
package pb
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for Backend service
|
||||
|
||||
type BackendClient interface {
|
||||
// Run MMF once. Return a matchobject that fits this profile.
|
||||
// INPUT: MatchObject message with these fields populated:
|
||||
// - id
|
||||
// - properties
|
||||
// - [optional] roster, any fields you fill are available to your MMF.
|
||||
// - [optional] pools, any fields you fill are available to your MMF.
|
||||
// OUTPUT: MatchObject message with these fields populated:
|
||||
// - id
|
||||
// - properties
|
||||
// - error. Empty if no error was encountered
|
||||
// - rosters, if you choose to fill them in your MMF. (Recommended)
|
||||
// - pools, if you used the MMLogicAPI in your MMF. (Recommended, and provides stats)
|
||||
CreateMatch(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*MatchObject, error)
|
||||
// Continually run MMF and stream matchobjects that fit this profile until
|
||||
// client closes the connection. Same inputs/outputs as CreateMatch.
|
||||
ListMatches(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (Backend_ListMatchesClient, error)
|
||||
// Delete a matchobject from state storage manually. (Matchobjects in state
|
||||
// storage will also automatically expire after a while)
|
||||
// INPUT: MatchObject message with the 'id' field populated.
|
||||
// (All other fields are ignored.)
|
||||
DeleteMatch(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*Result, error)
|
||||
// Write the connection info for the list of players in the
|
||||
// Assignments.messages.Rosters to state storage. The FrontendAPI is
|
||||
// responsible for sending anything sent here to the game clients.
|
||||
// Sending a player to this function kicks off a process that removes
|
||||
// the player from future matchmaking functions by adding them to the
|
||||
// 'deindexed' player list and then deleting their player ID from state storage
|
||||
// indexes.
|
||||
// INPUT: Assignments message with these fields populated:
|
||||
// - connection_info, anything you write to this string is sent to Frontend API
|
||||
// - rosters. You can send any number of rosters, containing any number of
|
||||
// player messages. All players from all rosters will be sent the connection_info.
|
||||
// The only field in the Player object that is used by CreateAssignments is
|
||||
// the id field. All others are silently ignored.
|
||||
CreateAssignments(ctx context.Context, in *Assignments, opts ...grpc.CallOption) (*Result, error)
|
||||
// Remove DGS connection info from state storage for players.
|
||||
// INPUT: Roster message with the 'players' field populated.
|
||||
// The only field in the Player object that is used by
|
||||
// DeleteAssignments is the 'id' field. All others are silently ignored. If
|
||||
// you need to delete multiple rosters, make multiple calls.
|
||||
DeleteAssignments(ctx context.Context, in *Roster, opts ...grpc.CallOption) (*Result, error)
|
||||
}
|
||||
|
||||
type backendClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewBackendClient(cc *grpc.ClientConn) BackendClient {
|
||||
return &backendClient{cc}
|
||||
}
|
||||
|
||||
func (c *backendClient) CreateMatch(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*MatchObject, error) {
|
||||
out := new(MatchObject)
|
||||
err := grpc.Invoke(ctx, "/api.Backend/CreateMatch", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *backendClient) ListMatches(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (Backend_ListMatchesClient, error) {
|
||||
stream, err := grpc.NewClientStream(ctx, &_Backend_serviceDesc.Streams[0], c.cc, "/api.Backend/ListMatches", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &backendListMatchesClient{stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Backend_ListMatchesClient interface {
|
||||
Recv() (*MatchObject, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type backendListMatchesClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *backendListMatchesClient) Recv() (*MatchObject, error) {
|
||||
m := new(MatchObject)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *backendClient) DeleteMatch(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/api.Backend/DeleteMatch", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *backendClient) CreateAssignments(ctx context.Context, in *Assignments, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/api.Backend/CreateAssignments", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *backendClient) DeleteAssignments(ctx context.Context, in *Roster, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/api.Backend/DeleteAssignments", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Backend service
|
||||
|
||||
type BackendServer interface {
|
||||
// Run MMF once. Return a matchobject that fits this profile.
|
||||
// INPUT: MatchObject message with these fields populated:
|
||||
// - id
|
||||
// - properties
|
||||
// - [optional] roster, any fields you fill are available to your MMF.
|
||||
// - [optional] pools, any fields you fill are available to your MMF.
|
||||
// OUTPUT: MatchObject message with these fields populated:
|
||||
// - id
|
||||
// - properties
|
||||
// - error. Empty if no error was encountered
|
||||
// - rosters, if you choose to fill them in your MMF. (Recommended)
|
||||
// - pools, if you used the MMLogicAPI in your MMF. (Recommended, and provides stats)
|
||||
CreateMatch(context.Context, *MatchObject) (*MatchObject, error)
|
||||
// Continually run MMF and stream matchobjects that fit this profile until
|
||||
// client closes the connection. Same inputs/outputs as CreateMatch.
|
||||
ListMatches(*MatchObject, Backend_ListMatchesServer) error
|
||||
// Delete a matchobject from state storage manually. (Matchobjects in state
|
||||
// storage will also automatically expire after a while)
|
||||
// INPUT: MatchObject message with the 'id' field populated.
|
||||
// (All other fields are ignored.)
|
||||
DeleteMatch(context.Context, *MatchObject) (*Result, error)
|
||||
// Write the connection info for the list of players in the
|
||||
// Assignments.messages.Rosters to state storage. The FrontendAPI is
|
||||
// responsible for sending anything sent here to the game clients.
|
||||
// Sending a player to this function kicks off a process that removes
|
||||
// the player from future matchmaking functions by adding them to the
|
||||
// 'deindexed' player list and then deleting their player ID from state storage
|
||||
// indexes.
|
||||
// INPUT: Assignments message with these fields populated:
|
||||
// - connection_info, anything you write to this string is sent to Frontend API
|
||||
// - rosters. You can send any number of rosters, containing any number of
|
||||
// player messages. All players from all rosters will be sent the connection_info.
|
||||
// The only field in the Player object that is used by CreateAssignments is
|
||||
// the id field. All others are silently ignored.
|
||||
CreateAssignments(context.Context, *Assignments) (*Result, error)
|
||||
// Remove DGS connection info from state storage for players.
|
||||
// INPUT: Roster message with the 'players' field populated.
|
||||
// The only field in the Player object that is used by
|
||||
// DeleteAssignments is the 'id' field. All others are silently ignored. If
|
||||
// you need to delete multiple rosters, make multiple calls.
|
||||
DeleteAssignments(context.Context, *Roster) (*Result, error)
|
||||
}
|
||||
|
||||
func RegisterBackendServer(s *grpc.Server, srv BackendServer) {
|
||||
s.RegisterService(&_Backend_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Backend_CreateMatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MatchObject)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BackendServer).CreateMatch(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Backend/CreateMatch",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BackendServer).CreateMatch(ctx, req.(*MatchObject))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Backend_ListMatches_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(MatchObject)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(BackendServer).ListMatches(m, &backendListMatchesServer{stream})
|
||||
}
|
||||
|
||||
type Backend_ListMatchesServer interface {
|
||||
Send(*MatchObject) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type backendListMatchesServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *backendListMatchesServer) Send(m *MatchObject) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _Backend_DeleteMatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MatchObject)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BackendServer).DeleteMatch(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Backend/DeleteMatch",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BackendServer).DeleteMatch(ctx, req.(*MatchObject))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Backend_CreateAssignments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Assignments)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BackendServer).CreateAssignments(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Backend/CreateAssignments",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BackendServer).CreateAssignments(ctx, req.(*Assignments))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Backend_DeleteAssignments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Roster)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BackendServer).DeleteAssignments(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Backend/DeleteAssignments",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BackendServer).DeleteAssignments(ctx, req.(*Roster))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Backend_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "api.Backend",
|
||||
HandlerType: (*BackendServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "CreateMatch",
|
||||
Handler: _Backend_CreateMatch_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteMatch",
|
||||
Handler: _Backend_DeleteMatch_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CreateAssignments",
|
||||
Handler: _Backend_CreateAssignments_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteAssignments",
|
||||
Handler: _Backend_DeleteAssignments_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "ListMatches",
|
||||
Handler: _Backend_ListMatches_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "api/protobuf-spec/backend.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("api/protobuf-spec/backend.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 240 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0x3f, 0x4b, 0x04, 0x31,
|
||||
0x10, 0x47, 0xfd, 0x03, 0x0a, 0xd9, 0xc6, 0x0b, 0xd8, 0x5c, 0xa3, 0xd8, 0xdf, 0x46, 0x14, 0x51,
|
||||
0x0b, 0x15, 0xef, 0x04, 0x1b, 0x45, 0xb1, 0xb4, 0x4b, 0x72, 0x73, 0x7b, 0xd1, 0x24, 0x13, 0x32,
|
||||
0xb3, 0x5f, 0xcf, 0xcf, 0x26, 0xbb, 0x01, 0x5d, 0x71, 0x41, 0x6c, 0x1f, 0xef, 0xf1, 0x9b, 0x10,
|
||||
0x71, 0xa0, 0x93, 0x53, 0x29, 0x23, 0xa3, 0x69, 0x57, 0x33, 0x4a, 0x60, 0x95, 0xd1, 0xf6, 0x1d,
|
||||
0xe2, 0xb2, 0xee, 0xa9, 0xdc, 0xd6, 0xc9, 0x4d, 0x0f, 0x7f, 0x5b, 0x01, 0x88, 0x74, 0x03, 0x54,
|
||||
0xb4, 0x93, 0x8f, 0x2d, 0xb1, 0x3b, 0x2f, 0xa1, 0xbc, 0x12, 0xd5, 0x22, 0x83, 0x66, 0x78, 0xd4,
|
||||
0x6c, 0xd7, 0x72, 0xbf, 0xfe, 0x72, 0x7b, 0xf0, 0x64, 0xde, 0xc0, 0xf2, 0x74, 0x1c, 0x1f, 0x6d,
|
||||
0xc8, 0x1b, 0x51, 0x3d, 0x38, 0xe2, 0x1e, 0x02, 0xfd, 0x37, 0x3f, 0xde, 0x94, 0x17, 0xa2, 0xba,
|
||||
0x03, 0x0f, 0x7f, 0xec, 0xef, 0x7d, 0xe3, 0x17, 0xa0, 0xd6, 0x77, 0xd3, 0xd7, 0x62, 0x52, 0x2e,
|
||||
0xbf, 0x25, 0x72, 0x4d, 0x0c, 0x10, 0xf9, 0xc7, 0x01, 0x03, 0x3c, 0xda, 0x5f, 0x8a, 0x49, 0x59,
|
||||
0x1e, 0xf6, 0x43, 0x11, 0x89, 0x21, 0x8f, 0xa5, 0xf3, 0xf3, 0xd7, 0xb3, 0xc6, 0xf1, 0xba, 0x35,
|
||||
0xb5, 0xc5, 0xa0, 0xee, 0x11, 0x1b, 0x0f, 0x0b, 0x8f, 0xed, 0xf2, 0xd9, 0x6b, 0x5e, 0x61, 0x0e,
|
||||
0x0a, 0x13, 0xc4, 0x59, 0xe8, 0x5e, 0xa0, 0x5c, 0x64, 0xc8, 0x51, 0x7b, 0x95, 0x8c, 0xd9, 0xe9,
|
||||
0x3f, 0xe0, 0xf4, 0x33, 0x00, 0x00, 0xff, 0xff, 0x33, 0x3a, 0x1e, 0x0d, 0xca, 0x01, 0x00, 0x00,
|
||||
}
|
260
internal/pb/frontend.pb.go
Normal file
260
internal/pb/frontend.pb.go
Normal file
@ -0,0 +1,260 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: api/protobuf-spec/frontend.proto
|
||||
|
||||
package pb
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// Data structure for a group of players to pass to the matchmaking function.
|
||||
// Obviously, the group can be a group of one!
|
||||
type Group struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
Properties string `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Group) Reset() { *m = Group{} }
|
||||
func (m *Group) String() string { return proto.CompactTextString(m) }
|
||||
func (*Group) ProtoMessage() {}
|
||||
func (*Group) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} }
|
||||
|
||||
func (m *Group) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Group) GetProperties() string {
|
||||
if m != nil {
|
||||
return m.Properties
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type PlayerId struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PlayerId) Reset() { *m = PlayerId{} }
|
||||
func (m *PlayerId) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlayerId) ProtoMessage() {}
|
||||
func (*PlayerId) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{1} }
|
||||
|
||||
func (m *PlayerId) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Group)(nil), "api.Group")
|
||||
proto.RegisterType((*PlayerId)(nil), "api.PlayerId")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for Frontend service
|
||||
|
||||
type FrontendClient interface {
|
||||
CreateRequest(ctx context.Context, in *Group, opts ...grpc.CallOption) (*Result, error)
|
||||
DeleteRequest(ctx context.Context, in *Group, opts ...grpc.CallOption) (*Result, error)
|
||||
GetAssignment(ctx context.Context, in *PlayerId, opts ...grpc.CallOption) (*ConnectionInfo, error)
|
||||
DeleteAssignment(ctx context.Context, in *PlayerId, opts ...grpc.CallOption) (*Result, error)
|
||||
}
|
||||
|
||||
type frontendClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewFrontendClient(cc *grpc.ClientConn) FrontendClient {
|
||||
return &frontendClient{cc}
|
||||
}
|
||||
|
||||
func (c *frontendClient) CreateRequest(ctx context.Context, in *Group, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/api.Frontend/CreateRequest", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *frontendClient) DeleteRequest(ctx context.Context, in *Group, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/api.Frontend/DeleteRequest", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *frontendClient) GetAssignment(ctx context.Context, in *PlayerId, opts ...grpc.CallOption) (*ConnectionInfo, error) {
|
||||
out := new(ConnectionInfo)
|
||||
err := grpc.Invoke(ctx, "/api.Frontend/GetAssignment", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *frontendClient) DeleteAssignment(ctx context.Context, in *PlayerId, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/api.Frontend/DeleteAssignment", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Frontend service
|
||||
|
||||
type FrontendServer interface {
|
||||
CreateRequest(context.Context, *Group) (*Result, error)
|
||||
DeleteRequest(context.Context, *Group) (*Result, error)
|
||||
GetAssignment(context.Context, *PlayerId) (*ConnectionInfo, error)
|
||||
DeleteAssignment(context.Context, *PlayerId) (*Result, error)
|
||||
}
|
||||
|
||||
func RegisterFrontendServer(s *grpc.Server, srv FrontendServer) {
|
||||
s.RegisterService(&_Frontend_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Frontend_CreateRequest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Group)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(FrontendServer).CreateRequest(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Frontend/CreateRequest",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(FrontendServer).CreateRequest(ctx, req.(*Group))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Frontend_DeleteRequest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Group)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(FrontendServer).DeleteRequest(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Frontend/DeleteRequest",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(FrontendServer).DeleteRequest(ctx, req.(*Group))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Frontend_GetAssignment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PlayerId)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(FrontendServer).GetAssignment(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Frontend/GetAssignment",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(FrontendServer).GetAssignment(ctx, req.(*PlayerId))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Frontend_DeleteAssignment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PlayerId)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(FrontendServer).DeleteAssignment(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.Frontend/DeleteAssignment",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(FrontendServer).DeleteAssignment(ctx, req.(*PlayerId))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Frontend_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "api.Frontend",
|
||||
HandlerType: (*FrontendServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "CreateRequest",
|
||||
Handler: _Frontend_CreateRequest_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteRequest",
|
||||
Handler: _Frontend_DeleteRequest_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetAssignment",
|
||||
Handler: _Frontend_GetAssignment_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteAssignment",
|
||||
Handler: _Frontend_DeleteAssignment_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "api/protobuf-spec/frontend.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("api/protobuf-spec/frontend.proto", fileDescriptor1) }
|
||||
|
||||
var fileDescriptor1 = []byte{
|
||||
// 278 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0xd0, 0x4f, 0x4b, 0xc3, 0x40,
|
||||
0x10, 0x05, 0xf0, 0x26, 0xa2, 0xd4, 0x85, 0x48, 0xd9, 0x53, 0xc8, 0x41, 0x4a, 0x4e, 0x5e, 0x9a,
|
||||
0x05, 0xa5, 0x14, 0xbc, 0x69, 0xc4, 0xd0, 0x5b, 0xc9, 0xd1, 0xdb, 0x26, 0x99, 0xa4, 0x0b, 0x9b,
|
||||
0x9d, 0x75, 0x77, 0xf6, 0xe0, 0xa7, 0xf5, 0xab, 0x88, 0xa9, 0xff, 0x90, 0x0a, 0x5e, 0x1f, 0xf3,
|
||||
0xe3, 0x3d, 0x86, 0x2d, 0xa5, 0x55, 0xc2, 0x3a, 0x24, 0x6c, 0x42, 0xbf, 0xf2, 0x16, 0x5a, 0xd1,
|
||||
0x3b, 0x34, 0x04, 0xa6, 0x2b, 0xa6, 0x98, 0x9f, 0x48, 0xab, 0xb2, 0x23, 0x67, 0x23, 0x78, 0x2f,
|
||||
0x07, 0xf0, 0x87, 0xb3, 0x7c, 0xc3, 0x4e, 0x2b, 0x87, 0xc1, 0xf2, 0x0b, 0x16, 0xab, 0x2e, 0x8d,
|
||||
0x96, 0xd1, 0xd5, 0x79, 0x1d, 0xab, 0x8e, 0x5f, 0x32, 0x66, 0x1d, 0x5a, 0x70, 0xa4, 0xc0, 0xa7,
|
||||
0xf1, 0x94, 0xff, 0x48, 0xf2, 0x8c, 0xcd, 0x77, 0x5a, 0xbe, 0x80, 0xdb, 0x76, 0xbf, 0xed, 0xf5,
|
||||
0x6b, 0xc4, 0xe6, 0x8f, 0x1f, 0x73, 0xb8, 0x60, 0x49, 0xe9, 0x40, 0x12, 0xd4, 0xf0, 0x1c, 0xc0,
|
||||
0x13, 0x67, 0x85, 0xb4, 0xaa, 0x98, 0x5a, 0xb3, 0x45, 0xf1, 0xb5, 0xa7, 0x06, 0x1f, 0x34, 0xe5,
|
||||
0xb3, 0x77, 0xf0, 0x00, 0x1a, 0xfe, 0x0f, 0x6e, 0x59, 0x52, 0x01, 0xdd, 0x79, 0xaf, 0x06, 0x33,
|
||||
0x82, 0x21, 0x9e, 0x4c, 0xe0, 0x73, 0x5e, 0x96, 0x7e, 0x9b, 0x12, 0x8d, 0x81, 0x96, 0x14, 0x9a,
|
||||
0xad, 0xe9, 0x31, 0x9f, 0xf1, 0x35, 0x5b, 0x1c, 0xca, 0xfe, 0xe6, 0x47, 0x2a, 0xef, 0x37, 0x4f,
|
||||
0xeb, 0x41, 0xd1, 0x3e, 0x34, 0x45, 0x8b, 0xa3, 0xa8, 0x10, 0x07, 0x0d, 0xa5, 0xc6, 0xd0, 0xed,
|
||||
0xb4, 0xa4, 0x1e, 0xdd, 0x28, 0xd0, 0x82, 0x59, 0x8d, 0x92, 0xda, 0xbd, 0x50, 0x86, 0xc0, 0x19,
|
||||
0xa9, 0x85, 0x6d, 0x9a, 0xb3, 0xe9, 0xed, 0x37, 0x6f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x97, 0x2e,
|
||||
0x6a, 0x58, 0xc1, 0x01, 0x00, 0x00,
|
||||
}
|
413
internal/pb/messages.pb.go
Normal file
413
internal/pb/messages.pb.go
Normal file
@ -0,0 +1,413 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: api/protobuf-spec/messages.proto
|
||||
|
||||
package pb
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// Open Match's internal representation and wire protocol format for "MatchObjects".
|
||||
// In order to request a match using the Backend API, your backend code should generate
|
||||
// a new MatchObject with an ID and properties filled in (for more details about valid
|
||||
// values for these fields, see the documentation). Open Match then sends the Match
|
||||
// Object through to your matchmaking function, where you add players to 'rosters' and
|
||||
// store any schemaless data you wish in the 'properties' field. The MatchObject
|
||||
// is then sent, populated, out through the Backend API to your backend code.
|
||||
//
|
||||
// MatchObjects contain a number of fields, but many gRPC calls that take a
|
||||
// MatchObject as input only require a few of them to be filled in. Check the
|
||||
// gRPC function in question for more details.
|
||||
type MatchObject struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
Properties string `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"`
|
||||
Error string `protobuf:"bytes,3,opt,name=error" json:"error,omitempty"`
|
||||
Rosters []*Roster `protobuf:"bytes,4,rep,name=rosters" json:"rosters,omitempty"`
|
||||
Pools []*PlayerPool `protobuf:"bytes,5,rep,name=pools" json:"pools,omitempty"`
|
||||
}
|
||||
|
||||
func (m *MatchObject) Reset() { *m = MatchObject{} }
|
||||
func (m *MatchObject) String() string { return proto.CompactTextString(m) }
|
||||
func (*MatchObject) ProtoMessage() {}
|
||||
func (*MatchObject) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0} }
|
||||
|
||||
func (m *MatchObject) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *MatchObject) GetProperties() string {
|
||||
if m != nil {
|
||||
return m.Properties
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *MatchObject) GetError() string {
|
||||
if m != nil {
|
||||
return m.Error
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *MatchObject) GetRosters() []*Roster {
|
||||
if m != nil {
|
||||
return m.Rosters
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MatchObject) GetPools() []*PlayerPool {
|
||||
if m != nil {
|
||||
return m.Pools
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Data structure to hold a list of players in a match.
|
||||
type Roster struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
Players []*Player `protobuf:"bytes,2,rep,name=players" json:"players,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Roster) Reset() { *m = Roster{} }
|
||||
func (m *Roster) String() string { return proto.CompactTextString(m) }
|
||||
func (*Roster) ProtoMessage() {}
|
||||
func (*Roster) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{1} }
|
||||
|
||||
func (m *Roster) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Roster) GetPlayers() []*Player {
|
||||
if m != nil {
|
||||
return m.Players
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A 'hard' filter to apply to the player pool.
|
||||
type Filter struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
Attribute string `protobuf:"bytes,2,opt,name=attribute" json:"attribute,omitempty"`
|
||||
Maxv int64 `protobuf:"varint,3,opt,name=maxv" json:"maxv,omitempty"`
|
||||
Minv int64 `protobuf:"varint,4,opt,name=minv" json:"minv,omitempty"`
|
||||
Stats *Stats `protobuf:"bytes,5,opt,name=stats" json:"stats,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Filter) Reset() { *m = Filter{} }
|
||||
func (m *Filter) String() string { return proto.CompactTextString(m) }
|
||||
func (*Filter) ProtoMessage() {}
|
||||
func (*Filter) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{2} }
|
||||
|
||||
func (m *Filter) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Filter) GetAttribute() string {
|
||||
if m != nil {
|
||||
return m.Attribute
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Filter) GetMaxv() int64 {
|
||||
if m != nil {
|
||||
return m.Maxv
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Filter) GetMinv() int64 {
|
||||
if m != nil {
|
||||
return m.Minv
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Filter) GetStats() *Stats {
|
||||
if m != nil {
|
||||
return m.Stats
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Holds statistics
|
||||
type Stats struct {
|
||||
Count int64 `protobuf:"varint,1,opt,name=count" json:"count,omitempty"`
|
||||
Elapsed float64 `protobuf:"fixed64,2,opt,name=elapsed" json:"elapsed,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Stats) Reset() { *m = Stats{} }
|
||||
func (m *Stats) String() string { return proto.CompactTextString(m) }
|
||||
func (*Stats) ProtoMessage() {}
|
||||
func (*Stats) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{3} }
|
||||
|
||||
func (m *Stats) GetCount() int64 {
|
||||
if m != nil {
|
||||
return m.Count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Stats) GetElapsed() float64 {
|
||||
if m != nil {
|
||||
return m.Elapsed
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// PlayerPools are defined by a set of 'hard' filters, and can be filled in
|
||||
// with the players that match those filters.
|
||||
//
|
||||
// PlayerPools contain a number of fields, but many gRPC calls that take a
|
||||
// PlayerPool as input only require a few of them to be filled in. Check the
|
||||
// gRPC function in question for more details.
|
||||
type PlayerPool struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
Filters []*Filter `protobuf:"bytes,2,rep,name=filters" json:"filters,omitempty"`
|
||||
Roster *Roster `protobuf:"bytes,3,opt,name=roster" json:"roster,omitempty"`
|
||||
Stats *Stats `protobuf:"bytes,4,opt,name=stats" json:"stats,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PlayerPool) Reset() { *m = PlayerPool{} }
|
||||
func (m *PlayerPool) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlayerPool) ProtoMessage() {}
|
||||
func (*PlayerPool) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{4} }
|
||||
|
||||
func (m *PlayerPool) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *PlayerPool) GetFilters() []*Filter {
|
||||
if m != nil {
|
||||
return m.Filters
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PlayerPool) GetRoster() *Roster {
|
||||
if m != nil {
|
||||
return m.Roster
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PlayerPool) GetStats() *Stats {
|
||||
if m != nil {
|
||||
return m.Stats
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Data structure to hold details about a player
|
||||
type Player struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
|
||||
Properties string `protobuf:"bytes,2,opt,name=properties" json:"properties,omitempty"`
|
||||
Pool string `protobuf:"bytes,3,opt,name=pool" json:"pool,omitempty"`
|
||||
Attributes []*Player_Attribute `protobuf:"bytes,4,rep,name=attributes" json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Player) Reset() { *m = Player{} }
|
||||
func (m *Player) String() string { return proto.CompactTextString(m) }
|
||||
func (*Player) ProtoMessage() {}
|
||||
func (*Player) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{5} }
|
||||
|
||||
func (m *Player) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Player) GetProperties() string {
|
||||
if m != nil {
|
||||
return m.Properties
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Player) GetPool() string {
|
||||
if m != nil {
|
||||
return m.Pool
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Player) GetAttributes() []*Player_Attribute {
|
||||
if m != nil {
|
||||
return m.Attributes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Player_Attribute struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
Value int64 `protobuf:"varint,2,opt,name=value" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Player_Attribute) Reset() { *m = Player_Attribute{} }
|
||||
func (m *Player_Attribute) String() string { return proto.CompactTextString(m) }
|
||||
func (*Player_Attribute) ProtoMessage() {}
|
||||
func (*Player_Attribute) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{5, 0} }
|
||||
|
||||
func (m *Player_Attribute) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Player_Attribute) GetValue() int64 {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Simple message to return success/failure and error status.
|
||||
type Result struct {
|
||||
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
|
||||
Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Result) Reset() { *m = Result{} }
|
||||
func (m *Result) String() string { return proto.CompactTextString(m) }
|
||||
func (*Result) ProtoMessage() {}
|
||||
func (*Result) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{6} }
|
||||
|
||||
func (m *Result) GetSuccess() bool {
|
||||
if m != nil {
|
||||
return m.Success
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Result) GetError() string {
|
||||
if m != nil {
|
||||
return m.Error
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IlInput is an empty message reserved for future use.
|
||||
type IlInput struct {
|
||||
}
|
||||
|
||||
func (m *IlInput) Reset() { *m = IlInput{} }
|
||||
func (m *IlInput) String() string { return proto.CompactTextString(m) }
|
||||
func (*IlInput) ProtoMessage() {}
|
||||
func (*IlInput) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{7} }
|
||||
|
||||
// Simple message used to pass the connection string for the DGS to the player.
|
||||
// DEPRECATED: Likely to be integrated into another protobuf message in a future version.
|
||||
type ConnectionInfo struct {
|
||||
ConnectionString string `protobuf:"bytes,1,opt,name=connection_string,json=connectionString" json:"connection_string,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ConnectionInfo) Reset() { *m = ConnectionInfo{} }
|
||||
func (m *ConnectionInfo) String() string { return proto.CompactTextString(m) }
|
||||
func (*ConnectionInfo) ProtoMessage() {}
|
||||
func (*ConnectionInfo) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{8} }
|
||||
|
||||
func (m *ConnectionInfo) GetConnectionString() string {
|
||||
if m != nil {
|
||||
return m.ConnectionString
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Assignments struct {
|
||||
Rosters []*Roster `protobuf:"bytes,1,rep,name=rosters" json:"rosters,omitempty"`
|
||||
ConnectionInfo *ConnectionInfo `protobuf:"bytes,2,opt,name=connection_info,json=connectionInfo" json:"connection_info,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Assignments) Reset() { *m = Assignments{} }
|
||||
func (m *Assignments) String() string { return proto.CompactTextString(m) }
|
||||
func (*Assignments) ProtoMessage() {}
|
||||
func (*Assignments) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{9} }
|
||||
|
||||
func (m *Assignments) GetRosters() []*Roster {
|
||||
if m != nil {
|
||||
return m.Rosters
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Assignments) GetConnectionInfo() *ConnectionInfo {
|
||||
if m != nil {
|
||||
return m.ConnectionInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*MatchObject)(nil), "messages.MatchObject")
|
||||
proto.RegisterType((*Roster)(nil), "messages.Roster")
|
||||
proto.RegisterType((*Filter)(nil), "messages.Filter")
|
||||
proto.RegisterType((*Stats)(nil), "messages.Stats")
|
||||
proto.RegisterType((*PlayerPool)(nil), "messages.PlayerPool")
|
||||
proto.RegisterType((*Player)(nil), "messages.Player")
|
||||
proto.RegisterType((*Player_Attribute)(nil), "messages.Player.Attribute")
|
||||
proto.RegisterType((*Result)(nil), "messages.Result")
|
||||
proto.RegisterType((*IlInput)(nil), "messages.IlInput")
|
||||
proto.RegisterType((*ConnectionInfo)(nil), "messages.ConnectionInfo")
|
||||
proto.RegisterType((*Assignments)(nil), "messages.Assignments")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("api/protobuf-spec/messages.proto", fileDescriptor3) }
|
||||
|
||||
var fileDescriptor3 = []byte{
|
||||
// 556 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xd1, 0x6a, 0xdb, 0x30,
|
||||
0x14, 0xc5, 0x49, 0xec, 0x36, 0x37, 0xd0, 0x76, 0x22, 0x0f, 0xa6, 0x8c, 0x11, 0x0c, 0x83, 0xd0,
|
||||
0xd1, 0x18, 0x32, 0x4a, 0xc7, 0x60, 0x0f, 0x59, 0x61, 0x5b, 0x1e, 0xc6, 0x82, 0xfa, 0xb6, 0x97,
|
||||
0x21, 0x3b, 0x4a, 0xaa, 0x21, 0x4b, 0x42, 0x92, 0xc3, 0x06, 0xfb, 0x81, 0x7d, 0xc4, 0xbe, 0x60,
|
||||
0x1f, 0xb1, 0x5f, 0x1b, 0x96, 0xec, 0xd8, 0xed, 0xda, 0x8d, 0xbd, 0xe9, 0x9e, 0x7b, 0xaf, 0x75,
|
||||
0xce, 0x3d, 0xba, 0x86, 0x09, 0x51, 0x2c, 0x55, 0x5a, 0x5a, 0x99, 0x95, 0x9b, 0x73, 0xa3, 0x68,
|
||||
0x9e, 0x16, 0xd4, 0x18, 0xb2, 0xa5, 0x66, 0xe6, 0x60, 0x74, 0xd8, 0xc4, 0xc9, 0xcf, 0x00, 0x46,
|
||||
0xef, 0x89, 0xcd, 0x6f, 0x3e, 0x64, 0x9f, 0x69, 0x6e, 0xd1, 0x11, 0xf4, 0xd8, 0x3a, 0x0e, 0x26,
|
||||
0xc1, 0x74, 0x88, 0x7b, 0x6c, 0x8d, 0x9e, 0x00, 0x28, 0x2d, 0x15, 0xd5, 0x96, 0x51, 0x13, 0xf7,
|
||||
0x1c, 0xde, 0x41, 0xd0, 0x18, 0x42, 0xaa, 0xb5, 0xd4, 0x71, 0xdf, 0xa5, 0x7c, 0x80, 0xce, 0xe0,
|
||||
0x40, 0x4b, 0x63, 0xa9, 0x36, 0xf1, 0x60, 0xd2, 0x9f, 0x8e, 0xe6, 0x27, 0xb3, 0x3d, 0x03, 0xec,
|
||||
0x12, 0xb8, 0x29, 0x40, 0x67, 0x10, 0x2a, 0x29, 0xb9, 0x89, 0x43, 0x57, 0x39, 0x6e, 0x2b, 0x57,
|
||||
0x9c, 0x7c, 0xa5, 0x7a, 0x25, 0x25, 0xc7, 0xbe, 0x24, 0x79, 0x07, 0x91, 0x6f, 0x47, 0x08, 0x06,
|
||||
0x82, 0x14, 0xb4, 0x66, 0xea, 0xce, 0xd5, 0xad, 0xca, 0xb5, 0x54, 0x44, 0xef, 0xdc, 0xea, 0xbf,
|
||||
0x85, 0x9b, 0x82, 0xe4, 0x7b, 0x00, 0xd1, 0x1b, 0xc6, 0x1f, 0xfa, 0xd4, 0x63, 0x18, 0x12, 0x6b,
|
||||
0x35, 0xcb, 0x4a, 0x4b, 0x6b, 0xd5, 0x2d, 0x50, 0x75, 0x14, 0xe4, 0xcb, 0xce, 0x69, 0xee, 0x63,
|
||||
0x77, 0x76, 0x18, 0x13, 0xbb, 0x78, 0x50, 0x63, 0x4c, 0xec, 0xd0, 0x53, 0x08, 0x8d, 0x25, 0xb6,
|
||||
0x92, 0x16, 0x4c, 0x47, 0xf3, 0xe3, 0x96, 0xce, 0x75, 0x05, 0x63, 0x9f, 0x4d, 0x2e, 0x21, 0x74,
|
||||
0x71, 0x35, 0xcc, 0x5c, 0x96, 0xc2, 0x3a, 0x2a, 0x7d, 0xec, 0x03, 0x14, 0xc3, 0x01, 0xe5, 0x44,
|
||||
0x19, 0xba, 0x76, 0x4c, 0x02, 0xdc, 0x84, 0xc9, 0x8f, 0x00, 0xa0, 0x1d, 0xd2, 0x43, 0x33, 0xd9,
|
||||
0x38, 0x99, 0xf7, 0xcc, 0xc4, 0xeb, 0xc7, 0x4d, 0x01, 0x9a, 0x42, 0xe4, 0x4d, 0x71, 0xc2, 0xee,
|
||||
0x33, 0xad, 0xce, 0xb7, 0xc2, 0x06, 0x7f, 0x15, 0xf6, 0x2b, 0x80, 0xc8, 0xf3, 0xfb, 0xef, 0x77,
|
||||
0x85, 0x60, 0x50, 0x59, 0x5e, 0x3f, 0x2b, 0x77, 0x46, 0x2f, 0x01, 0xf6, 0x1e, 0x34, 0x0f, 0xeb,
|
||||
0xf4, 0xae, 0xc5, 0xb3, 0x45, 0x53, 0x82, 0x3b, 0xd5, 0xa7, 0x17, 0x30, 0x5c, 0x74, 0xfd, 0xfb,
|
||||
0x63, 0x50, 0x63, 0x08, 0x77, 0x84, 0x97, 0xde, 0xed, 0x3e, 0xf6, 0x41, 0xf2, 0x02, 0x22, 0x4c,
|
||||
0x4d, 0xc9, 0x9d, 0x0b, 0xa6, 0xcc, 0x73, 0x6a, 0x8c, 0x6b, 0x3b, 0xc4, 0x4d, 0xd8, 0xae, 0x40,
|
||||
0xaf, 0xb3, 0x02, 0xc9, 0x10, 0x0e, 0x96, 0x7c, 0x29, 0x54, 0x69, 0x93, 0x57, 0x70, 0x74, 0x25,
|
||||
0x85, 0xa0, 0xb9, 0x65, 0x52, 0x2c, 0xc5, 0x46, 0xa2, 0x67, 0xf0, 0x28, 0xdf, 0x23, 0x9f, 0x8c,
|
||||
0xd5, 0x4c, 0x6c, 0x6b, 0x36, 0x27, 0x6d, 0xe2, 0xda, 0xe1, 0xc9, 0x37, 0x18, 0x2d, 0x8c, 0x61,
|
||||
0x5b, 0x51, 0x50, 0x61, 0x4d, 0x77, 0xb7, 0x82, 0x7f, 0xed, 0xd6, 0x02, 0x8e, 0x3b, 0xf7, 0x30,
|
||||
0xb1, 0x91, 0x8e, 0xe4, 0x68, 0x1e, 0xb7, 0x3d, 0xb7, 0xa9, 0xe1, 0xa3, 0xfc, 0x56, 0xfc, 0xfa,
|
||||
0xf2, 0xe3, 0xc5, 0x96, 0xd9, 0x9b, 0x32, 0x9b, 0xe5, 0xb2, 0x48, 0xdf, 0x4a, 0xb9, 0xe5, 0xf4,
|
||||
0x8a, 0xcb, 0x72, 0xbd, 0xe2, 0xc4, 0x6e, 0xa4, 0x2e, 0x52, 0xa9, 0xa8, 0x38, 0x2f, 0xaa, 0x7f,
|
||||
0x48, 0xca, 0x84, 0xa5, 0x5a, 0x10, 0x9e, 0xaa, 0x2c, 0x8b, 0xdc, 0xaf, 0xe6, 0xf9, 0xef, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0x29, 0x1e, 0x07, 0x0d, 0x8e, 0x04, 0x00, 0x00,
|
||||
}
|
382
internal/pb/mmlogic.pb.go
Normal file
382
internal/pb/mmlogic.pb.go
Normal file
@ -0,0 +1,382 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: api/protobuf-spec/mmlogic.proto
|
||||
/*
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package pb
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for MmLogic service
|
||||
|
||||
type MmLogicClient interface {
|
||||
// Send GetProfile a match object with the ID field populated, it will return a
|
||||
// 'filled' one.
|
||||
// Note: filters are assumed to have been checked for validity by the
|
||||
// backendapi when accepting a profile
|
||||
GetProfile(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*MatchObject, error)
|
||||
// CreateProposal is called by MMFs that wish to write their results to
|
||||
// a proposed MatchObject, that can be sent out the Backend API once it has
|
||||
// been approved (by default, by the evaluator process).
|
||||
// - adds all players in all Rosters to the proposed player ignore list
|
||||
// - writes the proposed match to the provided key
|
||||
// - adds that key to the list of proposals to be considered
|
||||
// INPUT:
|
||||
// * TO RETURN A MATCHOBJECT AFTER A SUCCESSFUL MMF RUN
|
||||
// To create a match MatchObject message with these fields populated:
|
||||
// - id, set to the value of the MMF_PROPOSAL_ID env var
|
||||
// - properties
|
||||
// - error. You must explicitly set this to an empty string if your MMF
|
||||
// - roster, with the playerIDs filled in the 'players' repeated field.
|
||||
// - [optional] pools, set to the output from the 'GetPlayerPools' call,
|
||||
// will populate the pools with stats about how many players the filters
|
||||
// matched and how long the filters took to run, which will be sent out
|
||||
// the backend api along with your match results.
|
||||
// was successful.
|
||||
// * TO RETURN AN ERROR
|
||||
// To report a failure or error, send a MatchObject message with these
|
||||
// these fields populated:
|
||||
// - id, set to the value of the MMF_ERROR_ID env var.
|
||||
// - error, set to a string value describing the error your MMF encountered.
|
||||
// - [optional] properties, anything you put here is returned to the
|
||||
// backend along with your error.
|
||||
// - [optional] rosters, anything you put here is returned to the
|
||||
// backend along with your error.
|
||||
// - [optional] pools, set to the output from the 'GetPlayerPools' call,
|
||||
// will populate the pools with stats about how many players the filters
|
||||
// matched and how long the filters took to run, which will be sent out
|
||||
// the backend api along with your match results.
|
||||
// OUTPUT: a Result message with a boolean success value and an error string
|
||||
// if an error was encountered
|
||||
CreateProposal(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*Result, error)
|
||||
// Player listing and filtering functions
|
||||
//
|
||||
// RetrievePlayerPool gets the list of players that match every Filter in the
|
||||
// PlayerPool, and then removes all players it finds in the ignore list. It
|
||||
// combines the results, and returns the resulting player pool.
|
||||
GetPlayerPool(ctx context.Context, in *PlayerPool, opts ...grpc.CallOption) (MmLogic_GetPlayerPoolClient, error)
|
||||
// Ignore List functions
|
||||
//
|
||||
// IlInput is an empty message reserved for future use.
|
||||
GetAllIgnoredPlayers(ctx context.Context, in *IlInput, opts ...grpc.CallOption) (*Roster, error)
|
||||
// RetrieveIgnoreList retrieves players from the ignore list specified in the
|
||||
// config file under 'ignoreLists.proposedPlayers.key'.
|
||||
ListIgnoredPlayers(ctx context.Context, in *IlInput, opts ...grpc.CallOption) (*Roster, error)
|
||||
}
|
||||
|
||||
type mmLogicClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewMmLogicClient(cc *grpc.ClientConn) MmLogicClient {
|
||||
return &mmLogicClient{cc}
|
||||
}
|
||||
|
||||
func (c *mmLogicClient) GetProfile(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*MatchObject, error) {
|
||||
out := new(MatchObject)
|
||||
err := grpc.Invoke(ctx, "/api.MmLogic/GetProfile", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *mmLogicClient) CreateProposal(ctx context.Context, in *MatchObject, opts ...grpc.CallOption) (*Result, error) {
|
||||
out := new(Result)
|
||||
err := grpc.Invoke(ctx, "/api.MmLogic/CreateProposal", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *mmLogicClient) GetPlayerPool(ctx context.Context, in *PlayerPool, opts ...grpc.CallOption) (MmLogic_GetPlayerPoolClient, error) {
|
||||
stream, err := grpc.NewClientStream(ctx, &_MmLogic_serviceDesc.Streams[0], c.cc, "/api.MmLogic/GetPlayerPool", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &mmLogicGetPlayerPoolClient{stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type MmLogic_GetPlayerPoolClient interface {
|
||||
Recv() (*PlayerPool, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type mmLogicGetPlayerPoolClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *mmLogicGetPlayerPoolClient) Recv() (*PlayerPool, error) {
|
||||
m := new(PlayerPool)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *mmLogicClient) GetAllIgnoredPlayers(ctx context.Context, in *IlInput, opts ...grpc.CallOption) (*Roster, error) {
|
||||
out := new(Roster)
|
||||
err := grpc.Invoke(ctx, "/api.MmLogic/GetAllIgnoredPlayers", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *mmLogicClient) ListIgnoredPlayers(ctx context.Context, in *IlInput, opts ...grpc.CallOption) (*Roster, error) {
|
||||
out := new(Roster)
|
||||
err := grpc.Invoke(ctx, "/api.MmLogic/ListIgnoredPlayers", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for MmLogic service
|
||||
|
||||
type MmLogicServer interface {
|
||||
// Send GetProfile a match object with the ID field populated, it will return a
|
||||
// 'filled' one.
|
||||
// Note: filters are assumed to have been checked for validity by the
|
||||
// backendapi when accepting a profile
|
||||
GetProfile(context.Context, *MatchObject) (*MatchObject, error)
|
||||
// CreateProposal is called by MMFs that wish to write their results to
|
||||
// a proposed MatchObject, that can be sent out the Backend API once it has
|
||||
// been approved (by default, by the evaluator process).
|
||||
// - adds all players in all Rosters to the proposed player ignore list
|
||||
// - writes the proposed match to the provided key
|
||||
// - adds that key to the list of proposals to be considered
|
||||
// INPUT:
|
||||
// * TO RETURN A MATCHOBJECT AFTER A SUCCESSFUL MMF RUN
|
||||
// To create a match MatchObject message with these fields populated:
|
||||
// - id, set to the value of the MMF_PROPOSAL_ID env var
|
||||
// - properties
|
||||
// - error. You must explicitly set this to an empty string if your MMF
|
||||
// - roster, with the playerIDs filled in the 'players' repeated field.
|
||||
// - [optional] pools, set to the output from the 'GetPlayerPools' call,
|
||||
// will populate the pools with stats about how many players the filters
|
||||
// matched and how long the filters took to run, which will be sent out
|
||||
// the backend api along with your match results.
|
||||
// was successful.
|
||||
// * TO RETURN AN ERROR
|
||||
// To report a failure or error, send a MatchObject message with these
|
||||
// these fields populated:
|
||||
// - id, set to the value of the MMF_ERROR_ID env var.
|
||||
// - error, set to a string value describing the error your MMF encountered.
|
||||
// - [optional] properties, anything you put here is returned to the
|
||||
// backend along with your error.
|
||||
// - [optional] rosters, anything you put here is returned to the
|
||||
// backend along with your error.
|
||||
// - [optional] pools, set to the output from the 'GetPlayerPools' call,
|
||||
// will populate the pools with stats about how many players the filters
|
||||
// matched and how long the filters took to run, which will be sent out
|
||||
// the backend api along with your match results.
|
||||
// OUTPUT: a Result message with a boolean success value and an error string
|
||||
// if an error was encountered
|
||||
CreateProposal(context.Context, *MatchObject) (*Result, error)
|
||||
// Player listing and filtering functions
|
||||
//
|
||||
// RetrievePlayerPool gets the list of players that match every Filter in the
|
||||
// PlayerPool, and then removes all players it finds in the ignore list. It
|
||||
// combines the results, and returns the resulting player pool.
|
||||
GetPlayerPool(*PlayerPool, MmLogic_GetPlayerPoolServer) error
|
||||
// Ignore List functions
|
||||
//
|
||||
// IlInput is an empty message reserved for future use.
|
||||
GetAllIgnoredPlayers(context.Context, *IlInput) (*Roster, error)
|
||||
// RetrieveIgnoreList retrieves players from the ignore list specified in the
|
||||
// config file under 'ignoreLists.proposedPlayers.key'.
|
||||
ListIgnoredPlayers(context.Context, *IlInput) (*Roster, error)
|
||||
}
|
||||
|
||||
func RegisterMmLogicServer(s *grpc.Server, srv MmLogicServer) {
|
||||
s.RegisterService(&_MmLogic_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _MmLogic_GetProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MatchObject)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MmLogicServer).GetProfile(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.MmLogic/GetProfile",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MmLogicServer).GetProfile(ctx, req.(*MatchObject))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _MmLogic_CreateProposal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MatchObject)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MmLogicServer).CreateProposal(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.MmLogic/CreateProposal",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MmLogicServer).CreateProposal(ctx, req.(*MatchObject))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _MmLogic_GetPlayerPool_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(PlayerPool)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(MmLogicServer).GetPlayerPool(m, &mmLogicGetPlayerPoolServer{stream})
|
||||
}
|
||||
|
||||
type MmLogic_GetPlayerPoolServer interface {
|
||||
Send(*PlayerPool) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type mmLogicGetPlayerPoolServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *mmLogicGetPlayerPoolServer) Send(m *PlayerPool) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _MmLogic_GetAllIgnoredPlayers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(IlInput)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MmLogicServer).GetAllIgnoredPlayers(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.MmLogic/GetAllIgnoredPlayers",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MmLogicServer).GetAllIgnoredPlayers(ctx, req.(*IlInput))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _MmLogic_ListIgnoredPlayers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(IlInput)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MmLogicServer).ListIgnoredPlayers(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/api.MmLogic/ListIgnoredPlayers",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MmLogicServer).ListIgnoredPlayers(ctx, req.(*IlInput))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _MmLogic_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "api.MmLogic",
|
||||
HandlerType: (*MmLogicServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "GetProfile",
|
||||
Handler: _MmLogic_GetProfile_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CreateProposal",
|
||||
Handler: _MmLogic_CreateProposal_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetAllIgnoredPlayers",
|
||||
Handler: _MmLogic_GetAllIgnoredPlayers_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListIgnoredPlayers",
|
||||
Handler: _MmLogic_ListIgnoredPlayers_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "GetPlayerPool",
|
||||
Handler: _MmLogic_GetPlayerPool_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "api/protobuf-spec/mmlogic.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("api/protobuf-spec/mmlogic.proto", fileDescriptor2) }
|
||||
|
||||
var fileDescriptor2 = []byte{
|
||||
// 263 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x91, 0x41, 0x4b, 0xf4, 0x30,
|
||||
0x10, 0x40, 0xf7, 0xfb, 0x04, 0x85, 0x80, 0xa2, 0x61, 0xbd, 0xf4, 0xa2, 0xec, 0x7d, 0x1b, 0x51,
|
||||
0xc4, 0x83, 0x8a, 0xe8, 0x1e, 0x4a, 0x61, 0x17, 0x8b, 0x47, 0x6f, 0x69, 0x9d, 0x76, 0x23, 0x93,
|
||||
0x4e, 0x48, 0x26, 0x07, 0x7f, 0x9e, 0xff, 0x4c, 0xd2, 0x05, 0x8b, 0x50, 0x2f, 0x5e, 0x5f, 0xde,
|
||||
0x9b, 0x09, 0x89, 0x38, 0xd3, 0xce, 0x28, 0xe7, 0x89, 0xa9, 0x8e, 0xed, 0x32, 0x38, 0x68, 0x94,
|
||||
0xb5, 0x48, 0x9d, 0x69, 0xf2, 0x81, 0xca, 0x3d, 0xed, 0x4c, 0x76, 0x3e, 0x61, 0x41, 0x08, 0xba,
|
||||
0x83, 0xb0, 0xd3, 0x2e, 0x3f, 0xff, 0x8b, 0x83, 0x8d, 0x5d, 0xa7, 0x50, 0xde, 0x09, 0x51, 0x00,
|
||||
0x57, 0x9e, 0x5a, 0x83, 0x20, 0x4f, 0xf3, 0x6f, 0x75, 0xa3, 0xb9, 0xd9, 0x3e, 0xd7, 0xef, 0xd0,
|
||||
0x70, 0x36, 0x8d, 0x17, 0x33, 0x79, 0x2b, 0x8e, 0x56, 0x1e, 0x34, 0x43, 0xe5, 0xc9, 0x51, 0xd0,
|
||||
0xf8, 0xdb, 0x84, 0xe3, 0x11, 0xbf, 0x40, 0x88, 0x98, 0xe2, 0x07, 0x71, 0x98, 0x56, 0xa3, 0xfe,
|
||||
0x00, 0x5f, 0x11, 0xa1, 0x9c, 0x8f, 0xd2, 0x48, 0xb3, 0x49, 0xba, 0x98, 0x5d, 0xfc, 0x93, 0xf7,
|
||||
0x62, 0x5e, 0x00, 0x3f, 0x22, 0x96, 0x5d, 0x4f, 0x1e, 0xde, 0x76, 0xc7, 0x41, 0x9e, 0x8c, 0x45,
|
||||
0x89, 0x65, 0xef, 0xe2, 0xcf, 0xfd, 0x14, 0x18, 0xfc, 0x70, 0x79, 0xb9, 0x36, 0x81, 0xff, 0x14,
|
||||
0x3f, 0xdd, 0xbc, 0x5e, 0x77, 0x86, 0xb7, 0xb1, 0xce, 0x1b, 0xb2, 0xaa, 0x20, 0xea, 0x10, 0x56,
|
||||
0x48, 0x31, 0xcd, 0xe1, 0x96, 0xbc, 0x55, 0xe4, 0xa0, 0x5f, 0xda, 0xf4, 0x06, 0xca, 0xf4, 0x0c,
|
||||
0xbe, 0xd7, 0xa8, 0x5c, 0x5d, 0xef, 0x0f, 0x7f, 0x70, 0xf5, 0x15, 0x00, 0x00, 0xff, 0xff, 0xf5,
|
||||
0xe5, 0x29, 0xa6, 0xcd, 0x01, 0x00, 0x00,
|
||||
}
|
88
internal/set/set.go
Normal file
88
internal/set/set.go
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
package apisrv provides an implementation of the gRPC server defined in ../../../api/protobuf-spec/mmlogic.proto.
|
||||
Most of the documentation for what these calls should do is in that file!
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
package set
|
||||
|
||||
// Intersection returns the interection of two sets.
|
||||
func Intersection(a []string, b []string) (out []string) {
|
||||
|
||||
hash := make(map[string]bool)
|
||||
|
||||
for _, v := range a {
|
||||
hash[v] = true
|
||||
}
|
||||
|
||||
for _, v := range b {
|
||||
if _, found := hash[v]; found {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
|
||||
}
|
||||
|
||||
// Union returns the union of two sets.
|
||||
func Union(a []string, b []string) (out []string) {
|
||||
|
||||
hash := make(map[string]bool)
|
||||
|
||||
// collect all values from input args
|
||||
for _, v := range a {
|
||||
hash[v] = true
|
||||
}
|
||||
|
||||
for _, v := range b {
|
||||
hash[v] = true
|
||||
}
|
||||
|
||||
// put values into string array
|
||||
for k := range hash {
|
||||
out = append(out, k)
|
||||
}
|
||||
|
||||
return out
|
||||
|
||||
}
|
||||
|
||||
// Difference returns the items in the first argument that are not in the
|
||||
// second (set 'a' - set 'b')
|
||||
func Difference(a []string, b []string) (out []string) {
|
||||
|
||||
hash := make(map[string]bool)
|
||||
out = append([]string{}, a...)
|
||||
|
||||
for _, v := range b {
|
||||
hash[v] = true
|
||||
}
|
||||
|
||||
// Iterate through output, removing items found in b
|
||||
for i := 0; i < len(out); i++ {
|
||||
if _, found := hash[out[i]]; found {
|
||||
// Remove this element by moving the copying the last element of the
|
||||
// array to this index and then slicing off the last element.
|
||||
// https://stackoverflow.com/a/37335777/3113674
|
||||
out[i] = out[len(out)-1]
|
||||
out = out[:len(out)-1]
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
85
internal/set/set_test.go
Normal file
85
internal/set/set_test.go
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
package set provides implementation of basic set operationg in golang.
|
||||
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package set
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
a1 [5]string
|
||||
a2 [4]string
|
||||
i [2]string
|
||||
u [7]string
|
||||
d [3]string
|
||||
)
|
||||
|
||||
type stringOperation func([]string, []string) []string
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
a1 = [5]string{"a", "b", "c", "d", "e"}
|
||||
a2 = [4]string{"b", "c", "f", "g"}
|
||||
i = [2]string{"b", "c"}
|
||||
u = [7]string{"a", "b", "c", "d", "e", "f", "g"}
|
||||
d = [3]string{"a", "d", "e"}
|
||||
|
||||
flag.Parse()
|
||||
exitCode := m.Run()
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func TestStringOperations(t *testing.T) {
|
||||
t.Run("Difference: valid slices", testStringOperation(Difference, a1[:], a2[:], d[:]))
|
||||
t.Run("Difference: remove nil", testStringOperation(Difference, a1[:], nil, a1[:]))
|
||||
t.Run("Difference: remove from nil", testStringOperation(Difference, nil, a2[:], nil))
|
||||
t.Run("Union: valid slices", testStringOperation(Union, a1[:], a2[:], u[:]))
|
||||
t.Run("Union: nil first", testStringOperation(Union, nil, a2[:], a2[:]))
|
||||
t.Run("Union: nil second", testStringOperation(Union, a1[:], nil, a1[:]))
|
||||
t.Run("Intersection: valid slices", testStringOperation(Intersection, a1[:], a2[:], i[:]))
|
||||
t.Run("Intersection: nil first", testStringOperation(Intersection, a1[:], nil, nil))
|
||||
t.Run("Intersection: nil second", testStringOperation(Intersection, nil, a2[:], nil))
|
||||
}
|
||||
|
||||
func testStringOperation(thisFunc stringOperation, in1 []string, in2 []string, outputExpected []string) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
t.Logf("func(%-15s , %-15s) = %v", fmt.Sprintf("%v", in1), fmt.Sprintf("%v", in2), outputExpected)
|
||||
outputActual := thisFunc(in1, in2)
|
||||
|
||||
if len(outputActual) != len(outputExpected) {
|
||||
t.Errorf("Length of output string slice incorrect, got %v, want %v", len(outputActual), outputExpected)
|
||||
}
|
||||
for x := 0; x < len(outputActual); x++ {
|
||||
found := false
|
||||
for y := 0; y < len(outputExpected) && found != true; y++ {
|
||||
if outputActual[x] == outputExpected[y] {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Element of output string slice incorrect, no %v in %v", outputActual[x], outputExpected)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
196
internal/statestorage/redis/ignorelist/ignorelist.go
Normal file
196
internal/statestorage/redis/ignorelist/ignorelist.go
Normal file
@ -0,0 +1,196 @@
|
||||
// Package ignorelist is an ignore list specific redis implementation and will be removed in a future version.
|
||||
/*
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Ignorelists are modeled in redis as sorted sets. Participant IDs are the
|
||||
elements, and the values are the epoch timestamp in seconds of when the element
|
||||
was added to the list.
|
||||
*/
|
||||
package ignorelist
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Logrus structured logging setup
|
||||
var (
|
||||
ilLogFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "statestorage",
|
||||
"caller": "statestorage/redis/ignorelist/ignorelist.go",
|
||||
}
|
||||
ilLog = log.WithFields(ilLogFields)
|
||||
)
|
||||
|
||||
// Create generates an ignorelist in redis by ZADD'ing elements with a score of
|
||||
// the current time in seconds since the epoch
|
||||
func Create(redisConn redis.Conn, ignorelistID string, playerIDs []string) error {
|
||||
|
||||
// Logrus logging
|
||||
cmd := "ZADD"
|
||||
|
||||
cmdArgs := buildElementValueList(ignorelistID, playerIDs)
|
||||
|
||||
ilLog.WithFields(log.Fields{
|
||||
"query": cmd,
|
||||
"args": cmdArgs,
|
||||
}).Debug("state storage operation")
|
||||
|
||||
// Run the Redis command.
|
||||
_, err := redisConn.Do(cmd, cmdArgs...)
|
||||
return err
|
||||
}
|
||||
|
||||
// SendAdd is identical to Add only does a redigo 'Send' as part of a MULTI command.
|
||||
func SendAdd(redisConn redis.Conn, ignorelistID string, playerIDs []string) {
|
||||
|
||||
// Logrus logging
|
||||
cmd := "ZADD"
|
||||
|
||||
cmdArgs := buildElementValueList(ignorelistID, playerIDs)
|
||||
|
||||
ilLog.WithFields(log.Fields{
|
||||
"query": cmd,
|
||||
"args": cmdArgs,
|
||||
}).Debug("state storage transaction operation")
|
||||
|
||||
// Run the Redis command.
|
||||
redisConn.Send(cmd, cmdArgs...)
|
||||
}
|
||||
|
||||
// Add is an alias for Create() in this implementation
|
||||
func Add(redisConn redis.Conn, ignorelistID string, playerIDs []string) error {
|
||||
return Create(redisConn, ignorelistID, playerIDs)
|
||||
}
|
||||
|
||||
// Remove deletes playerIDs from an ignorelist.
|
||||
func Remove(redisConn redis.Conn, ignorelistID string, playerIDs []string) error {
|
||||
|
||||
// Logrus logging
|
||||
cmd := "ZREM"
|
||||
|
||||
cmdArgs := buildElementValueList(ignorelistID, playerIDs)
|
||||
|
||||
ilLog.WithFields(log.Fields{
|
||||
"query": cmd,
|
||||
"args": cmdArgs,
|
||||
}).Debug("state storage operation")
|
||||
|
||||
// Run the Redis command.
|
||||
_, err := redisConn.Do(cmd, cmdArgs...)
|
||||
return err
|
||||
}
|
||||
|
||||
// SendRemove is identical to Remove only does a redigo 'Send' as part of a MULTI command.
|
||||
func SendRemove(redisConn redis.Conn, ignorelistID string, playerIDs []string) {
|
||||
|
||||
// Logrus logging
|
||||
cmd := "ZREM"
|
||||
|
||||
cmdArgs := buildElementValueList(ignorelistID, playerIDs)
|
||||
|
||||
ilLog.WithFields(log.Fields{
|
||||
"query": cmd,
|
||||
"args": cmdArgs,
|
||||
}).Debug("state storage transaction operation")
|
||||
|
||||
// Run the Redis command.
|
||||
redisConn.Send(cmd, cmdArgs...)
|
||||
}
|
||||
|
||||
// Retrieve returns a list of playerIDs in the ignorelist
|
||||
// 'cfg' is a viper.Sub sub-tree of the config file with just the parameters for
|
||||
// this ignorelist.
|
||||
func Retrieve(redisConn redis.Conn, cfg *viper.Viper, il string) ([]string, error) {
|
||||
|
||||
// String var init
|
||||
cmd := "ZRANGEBYSCORE"
|
||||
|
||||
ilName := il
|
||||
if cfg.IsSet("name") && cfg.GetString("name") != "" {
|
||||
ilName = cfg.GetString("name")
|
||||
}
|
||||
|
||||
// Timestamp var init
|
||||
now := time.Now().Unix()
|
||||
var minTS, maxTS int64
|
||||
|
||||
// Default to all players on the list
|
||||
maxTS = now
|
||||
minTS = 0
|
||||
|
||||
// Ignorelists are sorted sets with a 'score' that is the epoch timestamp
|
||||
// (in seconds) of when a player was added.
|
||||
// examples: Assume current timestamp is 1000001000
|
||||
// duration is 800 and offset is 0:
|
||||
// Ignore all players in the list with timestamps between 1000000200 - 1000001000
|
||||
// duration is 0 and offset is 500:
|
||||
// Ignore players in the list with timestamps between 0 - 1000000500
|
||||
if cfg.IsSet("offset") && cfg.GetInt64("offset") > 0 {
|
||||
maxTS = now - cfg.GetInt64("offset")
|
||||
}
|
||||
if cfg.IsSet("duration") && cfg.GetInt64("duration") > 0 {
|
||||
minTS = maxTS - cfg.GetInt64("duration")
|
||||
}
|
||||
|
||||
ilLog.WithFields(log.Fields{
|
||||
"query": cmd,
|
||||
"key": ilName,
|
||||
"minv": minTS,
|
||||
"maxv": maxTS,
|
||||
}).Debug("state storage transaction operation")
|
||||
|
||||
results, err := redis.Strings(redisConn.Do(cmd, ilName, minTS, maxTS))
|
||||
|
||||
return results, err
|
||||
}
|
||||
|
||||
func retrieve(redisConn redis.Conn, ignorelistID string, from int64, until int64) ([]string, error) {
|
||||
cmd := "ZRANGEBYSCORE"
|
||||
|
||||
// ignorelists are sorted sets with scores set to epoch timestamps (in seconds)
|
||||
results, err := redis.Strings(redisConn.Do(cmd, ignorelistID, from, until))
|
||||
return results, err
|
||||
}
|
||||
|
||||
// buildElemmentValueLIst builds an array of strings to send to the redis commad.
|
||||
// ZADD expects values (in this case we will use epoch timestamps) followed by
|
||||
// an element (we will use player IDs).
|
||||
//
|
||||
// Also, the redis module method requires that all the redis command
|
||||
// arguments be in a single variadic output, so we prepend the ignorelist ID
|
||||
// reference: https://stackoverflow.com/a/45279233/3113674
|
||||
// TODO: fold this into Create if we delete everything else that uses it, no
|
||||
// need for it to be it's own function in that case
|
||||
func buildElementValueList(ignorelistID string, playerIDs []string) []interface{} {
|
||||
// Build an array of strings to send to the redis commad. ZADD expects
|
||||
// values (in this case we will use epoch timestamps) followed by an
|
||||
// element (we will use player IDs).
|
||||
// Also, the redis module method requires that all the redis command
|
||||
// arguments be in a single variadic output, so prepend the ignorelist ID
|
||||
// reference: https://stackoverflow.com/a/45279233/3113674
|
||||
cmdArgs := make([]interface{}, 0)
|
||||
cmdArgs = append(cmdArgs, ignorelistID)
|
||||
now := time.Now().Unix()
|
||||
for _, pId := range playerIDs {
|
||||
cmdArgs = append(cmdArgs, strconv.Itoa(int(now)), pId)
|
||||
}
|
||||
return cmdArgs
|
||||
}
|
17
internal/statestorage/redis/ignorelist/ignorelist_test.go
Normal file
17
internal/statestorage/redis/ignorelist/ignorelist_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
package ignorelist
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rafaeljusto/redigomock"
|
||||
)
|
||||
|
||||
func TestCreate(*testing.T) {
|
||||
pids := "test1 test2 test3"
|
||||
ilid := "testil"
|
||||
redisConn := redigomock.NewConn()
|
||||
err := Create(redisConn, ilid, pids)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@ package playerq
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -30,8 +29,8 @@ import (
|
||||
var (
|
||||
pqLogFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "redishelpers",
|
||||
"caller": "statestorage/redis/redishelpers.go",
|
||||
"component": "statestorage",
|
||||
"caller": "statestorage/redis/playerq/playerq.go",
|
||||
}
|
||||
pqLog = log.WithFields(pqLogFields)
|
||||
)
|
||||
@ -63,11 +62,9 @@ func playerIndices(redisConn redis.Conn) (results []string, err error) {
|
||||
// "map.sunsetvalley": "123456782", // TRUE flag key, epoch timestamp value
|
||||
// "mode.ctf" // TRUE flag key, epoch timestamp value
|
||||
// }
|
||||
// TODO: Make indexing more customizable.
|
||||
func Create(redisConn redis.Conn, playerID string, playerData string) (err error) {
|
||||
func Create(redisConn redis.Conn, playerID string, playerData string) error {
|
||||
//pdJSON, err := json.Marshal(playerData)
|
||||
pdMap := redisValuetoMap(playerData)
|
||||
check(err, "")
|
||||
|
||||
redisConn.Send("MULTI")
|
||||
redisConn.Send("HSET", playerID, "properties", playerData)
|
||||
@ -78,13 +75,9 @@ func Create(redisConn redis.Conn, playerID string, playerData string) (err error
|
||||
// Add this index to the list of indices
|
||||
redisConn.Send("SADD", "indices", key)
|
||||
}
|
||||
|
||||
// Add the player to the built-in indexes
|
||||
redisConn.Send("SADD", "indices", "timestamp")
|
||||
redisConn.Send("ZADD", "timestamp", int(time.Now().Unix()), playerID)
|
||||
|
||||
_, err = redisConn.Do("EXEC")
|
||||
return
|
||||
_, err := redisConn.Do("EXEC")
|
||||
check(err, "")
|
||||
return err
|
||||
}
|
||||
|
||||
// Update is an alias for Create() in this implementation
|
||||
@ -119,25 +112,24 @@ func Delete(redisConn redis.Conn, playerID string) (err error) {
|
||||
redisConn.Send("MULTI")
|
||||
redisConn.Send("DEL", playerID)
|
||||
|
||||
// Remove playerID from generated indices
|
||||
// Remove playerID from indices
|
||||
for iName := range results {
|
||||
log.WithFields(log.Fields{
|
||||
"field": iName,
|
||||
"key": playerID}).Debug("Indexing field")
|
||||
"key": playerID}).Debug("De-Indexing field")
|
||||
redisConn.Send("ZREM", iName, playerID)
|
||||
}
|
||||
|
||||
// Remove the playerID from the built-in indexes
|
||||
redisConn.Send("ZREM", "timestamp", playerID)
|
||||
|
||||
_, err = redisConn.Do("EXEC")
|
||||
check(err, "")
|
||||
return
|
||||
}
|
||||
|
||||
// Unindex a player without deleting their JSON object representation from
|
||||
// state storage.
|
||||
func Unindex(redisConn redis.Conn, playerID string) (err error) {
|
||||
// Deindex a player without deleting there JSON object representation from
|
||||
// state storage. Unindexing is done in two stages: first the player is added to an ignore list, which 'atomically' removes them from consideration. A Goroutine is then kicked off to 'lazily' remove them from any field indicies that contain them.
|
||||
func Deindex(redisConn redis.Conn, playerID string) (err error) {
|
||||
|
||||
//TODO: remove deindexing from delete and call this instead
|
||||
|
||||
results, err := Retrieve(redisConn, playerID)
|
||||
if err != nil {
|
||||
log.Println("couldn't retreive player properties for ", playerID)
|
||||
@ -145,17 +137,13 @@ func Unindex(redisConn redis.Conn, playerID string) (err error) {
|
||||
|
||||
redisConn.Send("MULTI")
|
||||
|
||||
// Remove playerID from the generated indices
|
||||
// Remove playerID from indices
|
||||
for iName := range results {
|
||||
log.WithFields(log.Fields{
|
||||
"field": iName,
|
||||
"key": playerID}).Debug("Un-indexing field")
|
||||
redisConn.Send("ZREM", iName, playerID)
|
||||
}
|
||||
|
||||
// Remove the playerID from the built-in indexes
|
||||
redisConn.Send("ZREM", "timestamp", playerID)
|
||||
|
||||
_, err = redisConn.Do("EXEC")
|
||||
check(err, "")
|
||||
return
|
||||
|
@ -1,4 +1,4 @@
|
||||
// redisHelpers is a package for wrapping redis functionality.
|
||||
// Package redisHelpers is a package for wrapping redis functionality.
|
||||
/*
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
@ -49,7 +49,8 @@ func ConnectionPool(cfg *viper.Viper) *redis.Pool {
|
||||
|
||||
// Add redis user and password to connection url if they exist
|
||||
redisURL := "redis://"
|
||||
if cfg.IsSet("redis.user") && cfg.IsSet("redis.password") {
|
||||
if cfg.IsSet("redis.user") && cfg.GetString("redis.user") != "" &&
|
||||
cfg.IsSet("redis.password") && cfg.GetString("redis.password") != "" {
|
||||
redisURL += cfg.GetString("redis.user") + ":" + cfg.GetString("redis.password") + "@"
|
||||
}
|
||||
redisURL += cfg.GetString("redis.hostname") + ":" + cfg.GetString("redis.port")
|
||||
@ -62,6 +63,20 @@ func ConnectionPool(cfg *viper.Viper) *redis.Pool {
|
||||
Dial: func() (redis.Conn, error) { return redis.DialURL(redisURL) },
|
||||
}
|
||||
|
||||
// Sanity check that connection works before passing it back. Redigo
|
||||
// always returns a valid connection, and will just fail on the first
|
||||
// query: https://godoc.org/github.com/gomodule/redigo/redis#Pool.Get
|
||||
redisConn := pool.Get()
|
||||
defer redisConn.Close()
|
||||
_, err := redisConn.Do("SELECT", "0")
|
||||
// Encountered an issue getting a connection from the pool.
|
||||
if err != nil {
|
||||
rhLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"query": "SELECT 0"}).Error("state storage connection error")
|
||||
return nil
|
||||
}
|
||||
|
||||
rhLog.Info("Connected to Redis")
|
||||
return &pool
|
||||
}
|
||||
@ -100,7 +115,7 @@ func Watcher(ctx context.Context, pool *redis.Pool, key string) <-chan string {
|
||||
}
|
||||
}
|
||||
// Return value retreived from Redis asynchonously and tell calling function we're done
|
||||
rhLog.Debug("Statestorage watched record update detected")
|
||||
rhLog.Debug("state storage watched record update detected")
|
||||
watchChan <- results
|
||||
close(watchChan)
|
||||
}()
|
||||
@ -110,13 +125,18 @@ func Watcher(ctx context.Context, pool *redis.Pool, key string) <-chan string {
|
||||
|
||||
// Create is a concurrent-safe, context-aware redis SET of the input key to the
|
||||
// input value
|
||||
func Create(ctx context.Context, pool *redis.Pool, key string, value string) (string, error) {
|
||||
func Create(ctx context.Context, pool *redis.Pool, key string, values map[string]string) (string, error) {
|
||||
|
||||
// Add the key as a field to all logs for the execution of this function.
|
||||
rhLog = rhLog.WithFields(log.Fields{"key": key})
|
||||
|
||||
cmd := "SET"
|
||||
rhLog.WithFields(log.Fields{"query": cmd}).Debug("Statestorage operation")
|
||||
cmd := "HSET"
|
||||
|
||||
cLog := rhLog.WithFields(log.Fields{
|
||||
"query": cmd,
|
||||
"key": key,
|
||||
"values": values,
|
||||
})
|
||||
|
||||
// Get a connection to redis
|
||||
redisConn, err := pool.GetContext(ctx)
|
||||
@ -124,14 +144,22 @@ func Create(ctx context.Context, pool *redis.Pool, key string, value string) (st
|
||||
|
||||
// Encountered an issue getting a connection from the pool.
|
||||
if err != nil {
|
||||
rhLog.WithFields(log.Fields{
|
||||
cLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"query": cmd}).Error("Statestorage connection error")
|
||||
}).Error("state storage connection error")
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Build command arguments.
|
||||
cmdArgs := make([]interface{}, 0)
|
||||
cmdArgs = append(cmdArgs, key)
|
||||
for field, value := range values {
|
||||
cmdArgs = append(cmdArgs, field, value)
|
||||
}
|
||||
|
||||
// Run redis query and return
|
||||
return redis.String(redisConn.Do("SET", key, value))
|
||||
cLog.Debug("state storage operation")
|
||||
return redis.String(redisConn.Do(cmd, cmdArgs...))
|
||||
}
|
||||
|
||||
// Retrieve is a concurrent-safe, context-aware redis GET on the input key
|
||||
@ -141,7 +169,7 @@ func Retrieve(ctx context.Context, pool *redis.Pool, key string) (string, error)
|
||||
rhLog = rhLog.WithFields(log.Fields{"key": key})
|
||||
|
||||
cmd := "GET"
|
||||
rhLog.WithFields(log.Fields{"query": cmd}).Debug("Statestorage operation")
|
||||
rhLog.WithFields(log.Fields{"query": cmd}).Debug("state storage operation")
|
||||
|
||||
// Get a connection to redis
|
||||
redisConn, err := pool.GetContext(ctx)
|
||||
@ -151,12 +179,74 @@ func Retrieve(ctx context.Context, pool *redis.Pool, key string) (string, error)
|
||||
if err != nil {
|
||||
rhLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"query": cmd}).Error("Statestorage connection error")
|
||||
"query": cmd}).Error("state storage connection error")
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Run redis query and return
|
||||
return redis.String(redisConn.Do("GET", key))
|
||||
return redis.String(redisConn.Do(cmd, key))
|
||||
}
|
||||
|
||||
// RetrieveField is a concurrent-safe, context-aware redis HGET on the input field of the input key
|
||||
func RetrieveField(ctx context.Context, pool *redis.Pool, key string, field string) (string, error) {
|
||||
|
||||
// Add the key as a field to all logs for the execution of this function.
|
||||
rhLog = rhLog.WithFields(log.Fields{"key": key})
|
||||
|
||||
cmd := "HGET"
|
||||
|
||||
cLog := rhLog.WithFields(log.Fields{
|
||||
"query": cmd,
|
||||
"key": key,
|
||||
"field": field,
|
||||
})
|
||||
|
||||
// Get a connection to redis
|
||||
redisConn, err := pool.GetContext(ctx)
|
||||
defer redisConn.Close()
|
||||
|
||||
// Encountered an issue getting a connection from the pool.
|
||||
if err != nil {
|
||||
cLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
}).Error("state storage connection error")
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Run redis query and return
|
||||
cLog.Debug("state storage operation")
|
||||
return redis.String(redisConn.Do(cmd, key, field))
|
||||
}
|
||||
|
||||
// RetrieveAll is a concurrent-safe, context-aware redis HGETALL on the input key
|
||||
func RetrieveAll(ctx context.Context, pool *redis.Pool, key string) (map[string]string, error) {
|
||||
|
||||
// Add the key as a field to all logs for the execution of this function.
|
||||
rhLog = rhLog.WithFields(log.Fields{"key": key})
|
||||
|
||||
cmd := "HGETALL"
|
||||
|
||||
cLog := rhLog.WithFields(log.Fields{
|
||||
"query": cmd,
|
||||
"key": key,
|
||||
})
|
||||
|
||||
// Get a connection to redis
|
||||
redisConn, err := pool.GetContext(ctx)
|
||||
defer redisConn.Close()
|
||||
|
||||
// Encountered an issue getting a connection from the pool.
|
||||
if err != nil {
|
||||
cLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
}).Error("state storage connection error")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Run redis query and return
|
||||
cLog.Debug("state storage operation")
|
||||
return redis.StringMap(redisConn.Do(cmd, key))
|
||||
|
||||
}
|
||||
|
||||
// Update is a concurrent-safe, context-aware redis SADD of the input value to the
|
||||
@ -167,7 +257,7 @@ func Update(ctx context.Context, pool *redis.Pool, key string, value string) (st
|
||||
rhLog = rhLog.WithFields(log.Fields{"key": key})
|
||||
|
||||
cmd := "SADD"
|
||||
rhLog.WithFields(log.Fields{"query": cmd, "value": value}).Debug("Statestorage operation")
|
||||
rhLog.WithFields(log.Fields{"query": cmd, "value": value}).Debug("state storage operation")
|
||||
|
||||
// Get a connection to redis
|
||||
redisConn, err := pool.GetContext(ctx)
|
||||
@ -178,7 +268,7 @@ func Update(ctx context.Context, pool *redis.Pool, key string, value string) (st
|
||||
rhLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"query": cmd,
|
||||
"value": value}).Error("Statestorage connection error")
|
||||
"value": value}).Error("state storage connection error")
|
||||
return "", err
|
||||
}
|
||||
|
||||
@ -194,7 +284,7 @@ func Delete(ctx context.Context, pool *redis.Pool, key string) (string, error) {
|
||||
rhLog = rhLog.WithFields(log.Fields{"key": key})
|
||||
|
||||
cmd := "DEL"
|
||||
rhLog.WithFields(log.Fields{"query": cmd}).Debug("Statestorage operation")
|
||||
rhLog.WithFields(log.Fields{"query": cmd}).Debug("state storage operation")
|
||||
|
||||
// Get a connection to redis
|
||||
redisConn, err := pool.GetContext(ctx)
|
||||
@ -204,7 +294,7 @@ func Delete(ctx context.Context, pool *redis.Pool, key string) (string, error) {
|
||||
if err != nil {
|
||||
rhLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"query": cmd}).Error("Statestorage connection error")
|
||||
"query": cmd}).Error("state storage connection error")
|
||||
return "", err
|
||||
}
|
||||
|
||||
@ -219,7 +309,7 @@ func Count(ctx context.Context, pool *redis.Pool, key string) (int, error) {
|
||||
rhLog = rhLog.WithFields(log.Fields{"key": key})
|
||||
|
||||
cmd := "SCARD"
|
||||
rhLog.WithFields(log.Fields{"query": cmd}).Debug("Statestorage operation")
|
||||
rhLog.WithFields(log.Fields{"query": cmd}).Debug("state storage operation")
|
||||
|
||||
// Get a connection to redis
|
||||
redisConn, err := pool.GetContext(ctx)
|
||||
@ -229,7 +319,7 @@ func Count(ctx context.Context, pool *redis.Pool, key string) (int, error) {
|
||||
if err != nil {
|
||||
rhLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"query": cmd}).Error("Statestorage connection error")
|
||||
"query": cmd}).Error("state storage connection error")
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@ -238,13 +328,13 @@ func Count(ctx context.Context, pool *redis.Pool, key string) (int, error) {
|
||||
}
|
||||
|
||||
// Increment increments a redis value at key.
|
||||
func Increment(ctx context.Context, pool *redis.Pool, key string) (string, error) {
|
||||
func Increment(ctx context.Context, pool *redis.Pool, key string) (interface{}, error) {
|
||||
|
||||
// Add the key as a field to all logs for the execution of this function.
|
||||
rhLog = rhLog.WithFields(log.Fields{"key": key})
|
||||
|
||||
cmd := "INCR"
|
||||
rhLog.WithFields(log.Fields{"query": cmd}).Debug("Statestorage operation")
|
||||
rhLog.WithFields(log.Fields{"query": cmd}).Debug("state storage operation")
|
||||
|
||||
// Get a connection to redis
|
||||
redisConn, err := pool.GetContext(ctx)
|
||||
@ -254,22 +344,22 @@ func Increment(ctx context.Context, pool *redis.Pool, key string) (string, error
|
||||
if err != nil {
|
||||
rhLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"query": cmd}).Error("Statestorage connection error")
|
||||
"query": cmd}).Error("state storage connection error")
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Run redis query and return
|
||||
return redis.String(redisConn.Do("INCR", key))
|
||||
return redisConn.Do("INCR", key)
|
||||
}
|
||||
|
||||
// Decrement decrements a redis value at key.
|
||||
func Decrement(ctx context.Context, pool *redis.Pool, key string) (string, error) {
|
||||
func Decrement(ctx context.Context, pool *redis.Pool, key string) (interface{}, error) {
|
||||
|
||||
// Add the key as a field to all logs for the execution of this function.
|
||||
rhLog = rhLog.WithFields(log.Fields{"key": key})
|
||||
|
||||
cmd := "DECR"
|
||||
rhLog.WithFields(log.Fields{"query": cmd}).Debug("Statestorage operation")
|
||||
rhLog.WithFields(log.Fields{"query": cmd}).Debug("state storage operation")
|
||||
|
||||
// Get a connection to redis
|
||||
redisConn, err := pool.GetContext(ctx)
|
||||
@ -279,12 +369,12 @@ func Decrement(ctx context.Context, pool *redis.Pool, key string) (string, error
|
||||
if err != nil {
|
||||
rhLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"query": cmd}).Error("Statestorage connection error")
|
||||
"query": cmd}).Error("state storage connection error")
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Run redis query and return
|
||||
return redis.String(redisConn.Do("DECR", key))
|
||||
return redisConn.Do("DECR", key)
|
||||
}
|
||||
|
||||
// JSONStringToMap converts a JSON blob (which is how we store many things in
|
||||
|
217
internal/statestorage/redis/redispb/redispb.go
Normal file
217
internal/statestorage/redis/redispb/redispb.go
Normal file
@ -0,0 +1,217 @@
|
||||
// Package redispb marshals and unmarshals protobuf messages for redis state storage.
|
||||
// More details about the protobuf messages used in Open Match can be found in the api/protobuf-spec/om_messages.proto file.
|
||||
/*
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
All of this can probably be done more succinctly with some more interface and
|
||||
reflection, this is a hack but works for now.
|
||||
*/
|
||||
package redispb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
om_messages "github.com/GoogleCloudPlatform/open-match/internal/pb"
|
||||
"github.com/gogo/protobuf/jsonpb"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// Logrus structured logging setup
|
||||
var (
|
||||
rpLogFields = log.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "statestorage",
|
||||
"caller": "internal/statestorage/redis/redispb/redispb.go",
|
||||
}
|
||||
rpLog = log.WithFields(rpLogFields)
|
||||
)
|
||||
|
||||
// MarshalToRedis marshals a protobuf message to a redis hash.
|
||||
// The protobuf message in question must have an 'id' field.
|
||||
func MarshalToRedis(ctx context.Context, pb proto.Message, pool *redis.Pool) (err error) {
|
||||
|
||||
// We want to serialize to redis as JSON, not the typical protobuf string
|
||||
// serializer, so start by marshalling to json.
|
||||
this := jsonpb.Marshaler{}
|
||||
jsonMsg, err := this.MarshalToString(pb)
|
||||
if err != nil {
|
||||
rpLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
"protobuf": pb,
|
||||
}).Error("failure marshaling protobuf message to JSON")
|
||||
return
|
||||
}
|
||||
|
||||
// Get redis key
|
||||
keyResult := gjson.Get(jsonMsg, "id")
|
||||
|
||||
// Return error if the provided protobuf message doesn't have an ID field
|
||||
if !keyResult.Exists() {
|
||||
err = errors.New("cannot unmarshal protobuf messages without an id field")
|
||||
rpLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("failed to retrieve from redis")
|
||||
return
|
||||
}
|
||||
key := keyResult.String()
|
||||
|
||||
// Prepare redis command.
|
||||
cmd := "HSET"
|
||||
resultLog := rpLog.WithFields(log.Fields{
|
||||
"key": key,
|
||||
"cmd": cmd,
|
||||
})
|
||||
|
||||
// Get the Redis connection.
|
||||
redisConn, err := pool.GetContext(context.Background())
|
||||
defer redisConn.Close()
|
||||
if err != nil {
|
||||
rpLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("failed to connect to redis")
|
||||
return
|
||||
}
|
||||
redisConn.Send("MULTI")
|
||||
|
||||
// Write all non-id fields from the protobuf message to state storage.
|
||||
// Use reflection to get the field names from the protobuf message.
|
||||
pbInfo := reflect.ValueOf(pb).Elem()
|
||||
for i := 0; i < pbInfo.NumField(); i++ {
|
||||
// TODO: change this to use the json name field from the struct tags
|
||||
// something like parseTag() in src/encoding/json/tags.go
|
||||
//field := strings.ToLower(pbInfo.Type().Field(i).Tag.Get("json"))
|
||||
field := strings.ToLower(pbInfo.Type().Field(i).Name)
|
||||
value := gjson.Get(jsonMsg, field)
|
||||
if field != "id" {
|
||||
// This isn't the ID field, so write it to the redis hash.
|
||||
redisConn.Send(cmd, key, field, value)
|
||||
if err != nil {
|
||||
resultLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
"field": field,
|
||||
}).Error("State storage error")
|
||||
return
|
||||
}
|
||||
resultLog.WithFields(log.Fields{
|
||||
"component": "statestorage",
|
||||
"field": field,
|
||||
"value": value,
|
||||
}).Info("State storage operation")
|
||||
|
||||
}
|
||||
}
|
||||
_, err = redisConn.Do("EXEC")
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalFromRedis unmarshals a MatchObject from a redis hash.
|
||||
// This can probably be made generic to work with other pb messages in the future.
|
||||
// In every case where we don't get an update, we return an error.
|
||||
func UnmarshalFromRedis(ctx context.Context, pool *redis.Pool, pb *om_messages.MatchObject) error {
|
||||
|
||||
// Get the Redis connection.
|
||||
redisConn, err := pool.GetContext(context.Background())
|
||||
defer redisConn.Close()
|
||||
if err != nil {
|
||||
rpLog.WithFields(log.Fields{
|
||||
"error": err.Error(),
|
||||
"component": "statestorage",
|
||||
}).Error("failed to connect to redis")
|
||||
return err
|
||||
}
|
||||
|
||||
// Prepare redis command.
|
||||
cmd := "HGETALL"
|
||||
key := pb.Id
|
||||
resultLog := rpLog.WithFields(log.Fields{
|
||||
"component": "statestorage",
|
||||
"cmd": cmd,
|
||||
"key": key,
|
||||
})
|
||||
pbMap, err := redis.StringMap(redisConn.Do(cmd, key))
|
||||
pb.Error = pbMap["error"]
|
||||
pb.Properties = pbMap["properties"]
|
||||
poolsJSON := fmt.Sprintf("{\"pools\": %v}", pbMap["pools"])
|
||||
err = jsonpb.UnmarshalString(poolsJSON, pb)
|
||||
if err != nil {
|
||||
resultLog.Error("failure on pool")
|
||||
resultLog.Error(pbMap["pools"])
|
||||
resultLog.Error(err)
|
||||
}
|
||||
|
||||
rostersJSON := fmt.Sprintf("{\"rosters\": %v}", pbMap["rosters"])
|
||||
err = jsonpb.UnmarshalString(rostersJSON, pb)
|
||||
if err != nil {
|
||||
resultLog.Error("failure on roster")
|
||||
resultLog.Error(pbMap["rosters"])
|
||||
log.Error(err)
|
||||
}
|
||||
rpLog.Debug("Final pb:")
|
||||
rpLog.Debug(pb)
|
||||
return err
|
||||
}
|
||||
|
||||
// Watcher makes a channel and returns it immediately. It also launches an
|
||||
// asynchronous goroutine that watches a redis key and returns updates to
|
||||
// that key on the channel.
|
||||
//
|
||||
// The pattern for this function is from 'Go Concurrency Patterns', it is a function
|
||||
// that wraps a closure goroutine, and returns a channel.
|
||||
// reference: https://talks.golang.org/2012/concurrency.slide#25
|
||||
func Watcher(ctx context.Context, pool *redis.Pool, pb om_messages.MatchObject) <-chan om_messages.MatchObject {
|
||||
|
||||
watchChan := make(chan om_messages.MatchObject)
|
||||
results := om_messages.MatchObject{Id: pb.Id}
|
||||
|
||||
go func() {
|
||||
// var declaration
|
||||
var err = errors.New("haven't queried Redis yet")
|
||||
|
||||
// Loop, querying redis until this key has a value
|
||||
for err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Cleanup
|
||||
close(watchChan)
|
||||
return
|
||||
default:
|
||||
//results, err = Retrieve(ctx, pool, key)
|
||||
results = om_messages.MatchObject{Id: pb.Id}
|
||||
err = UnmarshalFromRedis(ctx, pool, &results)
|
||||
if err != nil {
|
||||
rpLog.Debug("No new results")
|
||||
time.Sleep(2 * time.Second) // TODO: exp bo + jitter
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return value retreived from Redis asynchonously and tell calling function we're done
|
||||
rpLog.Debug("state storage watched record update detected")
|
||||
watchChan <- results
|
||||
}()
|
||||
|
||||
return watchChan
|
||||
}
|
Reference in New Issue
Block a user