mirror of
https://github.com/googleforgames/open-match.git
synced 2025-03-22 19:08:31 +00:00
Compare commits
2 Commits
codeowner-
...
v0.7.0
Author | SHA1 | Date | |
---|---|---|---|
4f521b41db | |||
3c6183241e |
@ -33,6 +33,10 @@
|
||||
*swo
|
||||
*~
|
||||
|
||||
# Load testing residuals
|
||||
test/stress/*.csv
|
||||
test/stress/__pycache__
|
||||
|
||||
# Ping data files
|
||||
*.ping
|
||||
*.pings
|
||||
@ -48,8 +52,6 @@ detritus/
|
||||
|
||||
# Dotnet Core ignores
|
||||
*.swp
|
||||
*.pdb
|
||||
*.deps.json
|
||||
*.*~
|
||||
project.lock.json
|
||||
.DS_Store
|
||||
@ -80,9 +82,6 @@ bld/
|
||||
msbuild.log
|
||||
msbuild.err
|
||||
msbuild.wrn
|
||||
csharp/OpenMatch/obj
|
||||
Chart.lock
|
||||
|
||||
|
||||
# Visual Studio 2015
|
||||
.vs/
|
||||
@ -116,15 +115,16 @@ creds.json
|
||||
# Open Match Binaries
|
||||
cmd/backend/backend
|
||||
cmd/frontend/frontend
|
||||
cmd/query/query
|
||||
cmd/mmlogic/mmlogic
|
||||
cmd/synchronizer/synchronizer
|
||||
cmd/minimatch/minimatch
|
||||
cmd/swaggerui/swaggerui
|
||||
tools/certgen/certgen
|
||||
examples/demo/demo
|
||||
examples/functions/golang/soloduel/soloduel
|
||||
test/evaluator/evaluator
|
||||
test/matchfunction/matchfunction
|
||||
examples/functions/golang/rosterbased/rosterbased
|
||||
examples/functions/golang/pool/pool
|
||||
examples/evaluator/golang/simple/simple
|
||||
tools/reaper/reaper
|
||||
|
||||
# Open Match Build Directory
|
||||
@ -132,6 +132,3 @@ build/
|
||||
|
||||
# Secrets Directories
|
||||
install/helm/open-match/secrets/
|
||||
|
||||
# Helm tar charts
|
||||
install/helm/open-match/charts/
|
||||
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -1 +0,0 @@
|
||||
* @laremere @aLekSer @HazWard @calebatwd @syntxerror @sawagh @amg84 @scosgrave @mridulji
|
30
.github/ISSUE_TEMPLATE/bugreport.md
vendored
30
.github/ISSUE_TEMPLATE/bugreport.md
vendored
@ -1,30 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: kind/bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!
|
||||
|
||||
If the matter is security related, please disclose it privately via
|
||||
-->
|
||||
|
||||
|
||||
**What happened**:
|
||||
|
||||
**What you expected to happen**:
|
||||
|
||||
**How to reproduce it (as minimally and precisely as possible)**:
|
||||
|
||||
**Anything else we need to know?**:
|
||||
|
||||
**Output of `kubectl version`**:
|
||||
|
||||
**Cloud Provider/Platform (AKS, GKE, Minikube etc.)**:
|
||||
|
||||
**Open Match Release Version**:
|
||||
|
||||
**Install Method(yaml/helm)**:
|
20
.github/ISSUE_TEMPLATE/featurerequest.md
vendored
20
.github/ISSUE_TEMPLATE/featurerequest.md
vendored
@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: kind/feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
40
.github/ISSUE_TEMPLATE/release.md
vendored
40
.github/ISSUE_TEMPLATE/release.md
vendored
@ -1,5 +1,5 @@
|
||||
---
|
||||
name: Publish a Release
|
||||
name: release
|
||||
about: Instructions and checklist for creating a release.
|
||||
title: 'Release X.Y.Z-rc.N'
|
||||
labels: kind/release
|
||||
@ -91,7 +91,7 @@ Preview:
|
||||
Below this point you will see {version} used as a placeholder for future
|
||||
releases. Find {version} and replace with the current release (e.g. 0.5.0)
|
||||
|
||||
## Create a release branch in the upstream open-match repository
|
||||
## Create a release branch in the upstream repository
|
||||
|
||||
**Note: This step is performed by the person who starts the release. It is
|
||||
only required once.**
|
||||
@ -113,24 +113,16 @@ git push origin release-0.5
|
||||
- [ ] Open the [`cloudbuild.yaml`] and change the `_OM_VERSION` entry.
|
||||
- [ ] There might be additional references to the old version but be careful not to change it for places that have it for historical purposes.
|
||||
- [ ] Run `make release`
|
||||
- [ ] Run `make api/api.md` in open-match repo to update the auto-generated API references in open-match-docs repo.
|
||||
- [ ] Create a PR with the changes, include the release candidate name, and point it to the release branch.
|
||||
- [ ] Go to [open-match-build](https://pantheon.corp.google.com/cloud-build/triggers?project=open-match-build) and update all *post submit* triggers' `_GCB_LATEST_VERSION` value to the `X.Y` of the release. This value should only increase as it's used to determine the latest stable version.
|
||||
- [ ] Create a PR with the changes and include the release candidate name.
|
||||
- [ ] Go to [open-match-build](https://pantheon.corp.google.com/cloud-build/triggers?project=open-match-build) and update all the triggers' `_GCB_LATEST_VERSION` value to the `X.Y` of the release. This value should only increase as it's used to determine the latest stable version.
|
||||
- [ ] Merge your changes once the PR is approved.
|
||||
|
||||
## Create a release branch in the upstream open-match-docs repository
|
||||
- [ ] Open [`Makefile`](makefile-version) and change BASE_VERSION entry.
|
||||
- [ ] Open [`cloudbuild.yaml`] and change the `_OM_VERSION` entry.
|
||||
- [ ] Open [`site/config.toml`] and change the `release_version` entry.
|
||||
- [ ] Open [`site/static/swaggerui/config.json`] and change the `api/VERSION/...` entries
|
||||
- [ ] Create a PR with the changes, include the release candidate name, and point it to the release branch.
|
||||
|
||||
## Complete Milestone
|
||||
|
||||
**Note: This step is performed by the person who starts the release. It is
|
||||
only required once.**
|
||||
- [ ] Create the next [version milestone](https://github.com/googleforgames/open-match/milestones) and use [semantic versioning](https://semver.org/) when naming it to be consistent with the [Go community](https://blog.golang.org/versioning-proposal).
|
||||
- [ ] Create a *draft* [release](https://github.com/googleforgames/open-match/releases). Note that github has both "Pre-release" and "draft" as different concepts for a release. Until the release is finalized, only use "Save draft", and do not use "Publish release".
|
||||
- [ ] Create a *draft* [release](https://github.com/googleforgames/open-match/releases).
|
||||
- [ ] Use the [release template](https://github.com/googleforgames/open-match/blob/master/docs/governance/templates/release.md)
|
||||
- [ ] `Tag` = v{version}. Example: v0.5.0. Append -rc.# for release candidates. Example: v0.5.0-rc.1.
|
||||
- [ ] `Target` = release-X.Y. Example: release-0.5.
|
||||
@ -146,27 +138,17 @@ only required once.**
|
||||
- [ ] Review all closed issues against the milestone. Put the user visible changes into the release notes using the suggested format. https://github.com/googleforgames/open-match/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Av{version}
|
||||
- [ ] Verify the [milestone](https://github.com/googleforgames/open-match/milestones) is effectively 100% at this point with the exception of the release issue itself.
|
||||
|
||||
TODO: Add guidelines for labeling issues.
|
||||
|
||||
## Build Artifacts
|
||||
|
||||
- [ ] Go to the History section and find the "Post Submit" build of the merged commit that's running. Wait for it to go Green. If it's red, fix error repeat this section. Take note of the docker image version tag for next step. Example: 0.5.0-a4706cb.
|
||||
- [ ] Go to [Cloud Build](https://pantheon.corp.google.com/cloud-build/triggers?project=open-match-build), under Post Submit click "Run Trigger".
|
||||
- [ ] Go to the History section and find the "Post Submit" build that's running. Wait for it to go Green. If it's red, fix error repeat this section. Take note of the docker image version tag for next step. Example: 0.5.0-a4706cb.
|
||||
- [ ] Run `./docs/governance/templates/release.sh {source version tag} {version}` to copy the images to open-match-public-images.
|
||||
- [ ] If this is a new minor version in the newest major version then run `./docs/governance/templates/release.sh {source version tag} latest`.
|
||||
- [ ] Once the images have successfully been pushed to the registry, modify the line `open-match.dev/open-match v0.0.0-dev` in all `go.mod` files in the [Tutorials] (https://github.com/googleforgames/open-match/tree/main/tutorials) directory to use the current release version. This includes all solution subdirectories as well
|
||||
- [ ] Use the files under the `build/release/` directory for the Open Match installation guide. Make sure the artifacts work as expected - these are the artifacts that will be published to the GCS bucket and used in our release assets.
|
||||
- [ ] Copy the files from `build/release/` generated from `make release` to the release draft you created. You can drag and drop the files using the Github UI.
|
||||
- [ ] Update [Slack invitation link](https://slack.com/help/articles/201330256-invite-new-members-to-your-workspace#share-an-invite-link) in [open-match.dev](https://open-match.dev/site/docs/contribute/#get-involved).
|
||||
- [ ] Test Open Match installation under GKE and Minikube enviroment using YAML files and Helm. Follow the [First Match](https://development.open-match.dev/site/docs/getting-started/first_match/) guide, run `make proxy-demo`, and open `localhost:51507` to make sure everything works.
|
||||
- [ ] Minikube: Run `make create-mini-cluster` to create a local cluster with latest Kubernetes API version.
|
||||
- [ ] GKE: Run `make create-gke-cluster` to create a GKE cluster.
|
||||
- [ ] Helm: Run `helm install open-match -n open-match open-match/open-match`
|
||||
- [ ] Update usage requirements in the Installation doc - e.g. supported minikube version, kubectl version, golang version, etc.
|
||||
|
||||
## Finalize
|
||||
|
||||
- [ ] Save the release as a draft.
|
||||
- [ ] Circulate the draft release to active contributors. Where reasonable, get everyone's ok on the release notes before continuing.
|
||||
- [ ] Publish the [Release](om-release) in Github. This will notify repository watchers.
|
||||
- [ ] Publish the [Release](om-release) on Open Match [Blog](https://open-match.dev/site/blog/).
|
||||
- [ ] Open the [`README.md`](readme-deploy) update the version references and submit. (Release candidates can ignore this step.)
|
||||
- [ ] Publish the [Release](om-release) in Github.
|
||||
|
||||
## Announce
|
||||
|
||||
|
16
.github/pull_request_template.md
vendored
16
.github/pull_request_template.md
vendored
@ -1,16 +0,0 @@
|
||||
<!-- Thanks for sending a pull request! Here are some tips for you:
|
||||
If this is your first time, please read our contributor guidelines: https://github.com/googleforgames/open-match/blob/master/CONTRIBUTING.md and developer guide https://github.com/googleforgames/open-match/blob/master/docs/development.md
|
||||
-->
|
||||
|
||||
**What this PR does / Why we need it**:
|
||||
|
||||
**Which issue(s) this PR fixes**:
|
||||
<!--
|
||||
*Automatically closes linked issue when PR is merged.
|
||||
Usage: `Closes #<issue number>`, or `Closes (paste link of issue)`.
|
||||
-->
|
||||
Closes #
|
||||
|
||||
**Special notes for your reviewer**:
|
||||
|
||||
|
18
.gitignore
vendored
18
.gitignore
vendored
@ -31,6 +31,10 @@
|
||||
*swo
|
||||
*~
|
||||
|
||||
# Load testing residuals
|
||||
test/stress/*.csv
|
||||
test/stress/__pycache__
|
||||
|
||||
# Ping data files
|
||||
*.ping
|
||||
*.pings
|
||||
@ -46,8 +50,6 @@ detritus/
|
||||
|
||||
# Dotnet Core ignores
|
||||
*.swp
|
||||
*.pdb
|
||||
*.deps.json
|
||||
*.*~
|
||||
project.lock.json
|
||||
.DS_Store
|
||||
@ -78,8 +80,6 @@ bld/
|
||||
msbuild.log
|
||||
msbuild.err
|
||||
msbuild.wrn
|
||||
csharp/OpenMatch/obj
|
||||
Chart.lock
|
||||
|
||||
# Visual Studio 2015
|
||||
.vs/
|
||||
@ -112,19 +112,17 @@ creds.json
|
||||
# Open Match Binaries
|
||||
cmd/backend/backend
|
||||
cmd/frontend/frontend
|
||||
cmd/query/query
|
||||
cmd/mmlogic/mmlogic
|
||||
cmd/synchronizer/synchronizer
|
||||
cmd/minimatch/minimatch
|
||||
cmd/swaggerui/swaggerui
|
||||
tools/certgen/certgen
|
||||
examples/demo/demo
|
||||
examples/functions/golang/soloduel/soloduel
|
||||
test/evaluator/evaluator
|
||||
test/matchfunction/matchfunction
|
||||
examples/functions/golang/rosterbased/rosterbased
|
||||
examples/functions/golang/pool/pool
|
||||
examples/evaluator/golang/simple/simple
|
||||
tools/reaper/reaper
|
||||
|
||||
# Secrets Directories
|
||||
install/helm/open-match/secrets/
|
||||
|
||||
# Helm tar charts
|
||||
install/helm/open-match/charts/
|
||||
|
@ -16,8 +16,6 @@
|
||||
# with their default values.
|
||||
|
||||
# https://github.com/golangci/golangci-lint#config-file
|
||||
service:
|
||||
golangci-lint-version: 1.18.0
|
||||
|
||||
# options for analysis running
|
||||
run:
|
||||
@ -167,14 +165,19 @@ linters-settings:
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- goimports
|
||||
- stylecheck
|
||||
- gocritic
|
||||
- dupl
|
||||
- funlen
|
||||
- gochecknoglobals
|
||||
- goconst
|
||||
- gocyclo
|
||||
- gosec
|
||||
- interfacer # deprecated - "A tool that suggests interfaces is prone to bad suggestions"
|
||||
- lll
|
||||
- staticcheck
|
||||
- scopelint
|
||||
- prealloc
|
||||
- gofmt
|
||||
- interfacer # deprecated - "A tool that suggests interfaces is prone to bad suggestions"
|
||||
- gochecknoglobals
|
||||
|
||||
#linters:
|
||||
# enable-all: true
|
||||
|
60
CHANGELOG.md
Normal file
60
CHANGELOG.md
Normal file
@ -0,0 +1,60 @@
|
||||
# Release history
|
||||
|
||||
## v0.4.0 (alpha)
|
||||
|
||||
### Release notes
|
||||
- Thanks to completion of Issues [#42](issues/42) and [#45](issues/45), there is no longer a need to use the `openmatch-base` image when building components of Open Match. Each stand alone appliation now is self-contained in its `Dockerfile` and `cloudbuild.yaml` files, and builds have been substantially simplified. **Note**: The default `Dockerfile` and `cloudbuild.yaml` now tag their images with the version number, not `dev`, and the YAML files in the `install` directory now reflect this.
|
||||
- This paves the way for CI/CD in an upcoming version.
|
||||
- This paves the way for public images in an upcoming version!
|
||||
|
||||
## v0.3.0 (alpha)
|
||||
This update is focused on the Frontend API and Player Records, including more robust code for indexing, deindexing, reading, writing, and expiring player requests from Open Match state storage. All Frontend API function argument have changed, although many only slightly. 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))!
|
||||
|
||||
### Release notes
|
||||
- The Frontend API calls have all be changed to reflect the fact that they operate on Players in state storage. To queue a game client, 'CreatePlayer' in Open Match, to get updates 'GetUpdates', and to stop matching, 'DeletePlayer'. The calls are now much more obviously related to how Open Match sees players: they are database records that it creates on demand, updates using MMFs and the Backend API, and deletes when the player is no longer looking for a match.
|
||||
- The Player record in state storage has changed to a more complete hash format, and it no longer makes sense to remove a player's assignment from the Frontend as a separate action to removing their record entirely. `DeleteAssignment()` has therefore been removed. Just use `DeletePlayer` instead; you'll always want the client to re-request matching with its latest attributes anyway.
|
||||
- There is now a module for [indexing and deindexing players in state storage](internal/statestorage/redis/playerindices/playerindices.go). This is a *much* more efficient, as well as being cleaner and more maintainable than the previous implementation which was **hard-coded to index everything** you passed in to the Frontend API at a specific JSON object depth.
|
||||
- This paves the way for dynamically choosing your indicies without restarting the matchmaker. This will be implemented if there is demand. Pull Requests are welcome!
|
||||
- Two internal timestamp-based indices have replaced the previous `timestamp` index. `created` is used to calculate how long a player has been waiting for a match, `accessed` is used to determine when a player needs to be expired out of state storage. Both are prefixed by the string `OM_METADATA` so it should be easy to spot them.
|
||||
- A call to the Frontend API `GetUpdates()` gRPC endpoint returns a stream of player messages. This is used to send updates to state storage for the `Assignment`, `Status`, and `Error` Player fields in near-realtime. **It is the responsibility of the game client to disconnect** from the stream when it has gotten the results it was waiting for!
|
||||
- Moved the rest of the gRPC messages into a shared [`messages.proto` file](api/protobuf-spec/messages.proto).
|
||||
- Added documentation to Frontend API gRPC calls to the [`frontend.proto` file](api/protobuf-spec/frontend.proto).
|
||||
- [Issue #41](https://github.com/googleforgames/open-match/issues/41)|[PR #48](https://github.com/googleforgames/open-match/pull/48) There is now a HA Redis install available in `install/yaml/01-redis-failover.yaml`. This would be used as a drop-in replacement for a single-instance Redis configuration in `install/yaml/01-redis.yaml`. The HA configuration requires that you install the [Redis Operator](https://github.com/spotahome/redis-operator) (note: **currently alpha**, use at your own risk) in your Kubernetes cluster.
|
||||
- As part of this change, the kubernetes service name is now `redis` not `redis-sentinel` to denote that it is accessed using a standard Redis client.
|
||||
- Open Match uses a new feature of the go module [logrus](github.com/sirupsen/logrus) to include filenames and line numbers. If you have an older version in your local build environment, you may need to delete the module and `go get github.com/sirupsen/logrus` again. When building using the provided `cloudbuild.yaml` and `Dockerfile`s this is handled for you.
|
||||
- The program that was formerly in `examples/frontendclient` has been expanded and has been moved to the `test` directory under (`test/cmd/frontendclient/`)[test/cmd/frontendclient/].
|
||||
- The client load generator program has been moved from `test/cmd/client` to (`test/cmd/clientloadgen/`)[test/cmd/clientloadgen/] to better reflect what it does.
|
||||
- [Issue #45](https://github.com/googleforgames/open-match/issues/45) The process for moving the build files (`Dockerfile` and `cloudbuild.yaml`) for each component, example, and test program to their respective directories and out of the repository root has started but won't be completed until a future version.
|
||||
- Put some basic notes in the [production guide](docs/production.md)
|
||||
- Added a basic [roadmap](docs/roadmap.md)
|
||||
|
||||
## 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/googleforgames/open-match/issues/41) if it fills our needs, feel free to contribute!
|
||||
|
||||
## v0.1.0 (alpha)
|
||||
Initial release.
|
@ -12,8 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# When updating Go version, update Dockerfile.ci, Dockerfile.base-build, and go.mod
|
||||
FROM golang:1.14.0
|
||||
FROM golang:latest
|
||||
ENV GO111MODULE=on
|
||||
|
||||
WORKDIR /go/src/open-match.dev/open-match
|
||||
|
@ -34,13 +34,11 @@ RUN export CLOUD_SDK_REPO="cloud-sdk-stretch" && \
|
||||
apt-get update -y && apt-get install google-cloud-sdk google-cloud-sdk-app-engine-go -y -qq
|
||||
|
||||
# Install Golang
|
||||
# https://github.com/docker-library/golang/blob/master/1.14/stretch/Dockerfile
|
||||
# https://github.com/docker-library/golang/blob/fd272b2b72db82a0bd516ce3d09bba624651516c/1.12/stretch/Dockerfile
|
||||
RUN mkdir -p /toolchain/golang
|
||||
WORKDIR /toolchain/golang
|
||||
RUN sudo rm -rf /usr/local/go/
|
||||
|
||||
# When updating Go version, update Dockerfile.ci, Dockerfile.base-build, and go.mod
|
||||
RUN curl -L https://golang.org/dl/go1.14.linux-amd64.tar.gz | sudo tar -C /usr/local -xz
|
||||
RUN curl -L https://storage.googleapis.com/golang/go1.12.6.linux-amd64.tar.gz | sudo tar -C /usr/local -xz
|
||||
|
||||
ENV GOPATH /go
|
||||
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
|
||||
|
@ -18,9 +18,7 @@ WORKDIR /go/src/open-match.dev/open-match
|
||||
|
||||
ARG IMAGE_TITLE
|
||||
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
make "build/cmd/${IMAGE_TITLE}"
|
||||
RUN make "build/cmd/${IMAGE_TITLE}"
|
||||
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
ARG IMAGE_TITLE
|
||||
|
@ -24,9 +24,13 @@ The [Open Match Development guide](docs/development.md) has detailed instruction
|
||||
on getting the source code, making changes, testing and submitting a pull request
|
||||
to Open Match.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This software is currently alpha, and subject to change.
|
||||
|
||||
## Support
|
||||
|
||||
* [Slack Channel](https://open-match.slack.com/) ([Signup](https://join.slack.com/t/open-match/shared_invite/enQtNDM1NjcxNTY4MTgzLTM5ZWQxNjc1YWI3MzJmN2RiMWJmYWI0ZjFiNzNkZmNkMWQ3YWU5OGVkNzA5Yzc4OGVkOGU5MTc0OTA5ZTA5NDU))
|
||||
* [Slack Channel](https://open-match.slack.com/) ([Signup](https://join.slack.com/t/open-match/shared_invite/enQtNDM1NjcxNTY4MTgzLWQzMzE1MGY5YmYyYWY3ZjE2MjNjZTdmYmQ1ZTQzMmNiNGViYmQyN2M4ZmVkMDY2YzZlOTUwMTYwMzI1Y2I2MjU))
|
||||
* [File an Issue](https://github.com/googleforgames/open-match/issues/new)
|
||||
* [Mailing list](https://groups.google.com/forum/#!forum/open-match-discuss)
|
||||
|
||||
|
@ -6,10 +6,4 @@ gRPC has first-class support for [many languages](https://grpc.io/docs/) and pro
|
||||
|
||||
For HTTP/HTTPS Open Match uses a gRPC proxy to serve the API. Since HTTP does not provide a structure for request/responses we use Swagger to provide a schema. You can view the Swagger docs for each service in this directory's `*.swagger.json` files. In addition each server will host it's swagger doc via `GET /swagger.json` if you want to dynamically load them at runtime.
|
||||
|
||||
Lastly, Open Match supports insecure and TLS mode for serving the API. It's strongly preferred to use TLS mode in production but insecure mode can be used for test and local development. To help with certificates management see `tools/certgen` to create self-signed certificates.
|
||||
|
||||
# Open Match API Development Guide
|
||||
|
||||
Open Match proto comments follow the same format as [this file](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/example.proto)
|
||||
|
||||
If you plan to change the proto definitions, please update the comments and run `make api/api.md` to reflect the latest changes in open-match-docs.
|
||||
Lastly, Open Match supports insecure and TLS mode for serving the API. It's strongly preferred to use TLS mode in production but insecure mode can be used for test and local development. To help with certificates management see `tools/certgen` to create self-signed certificates.
|
@ -19,9 +19,9 @@ option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "api/messages.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "protoc-gen-swagger/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "Backend"
|
||||
version: "1.0"
|
||||
@ -55,7 +55,8 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/proto/examplepb/a_bit_of_everything.proto
|
||||
};
|
||||
|
||||
// FunctionConfig specifies a MMF address and client type for Backend to establish connections with the MMF
|
||||
// Configuration for the Match Function to be triggered by Open Match to
|
||||
// generate proposals.
|
||||
message FunctionConfig {
|
||||
string host = 1;
|
||||
int32 port = 2;
|
||||
@ -67,102 +68,48 @@ message FunctionConfig {
|
||||
}
|
||||
|
||||
message FetchMatchesRequest {
|
||||
// A configuration for the MatchFunction server of this FetchMatches call.
|
||||
// Configuration of the MatchFunction to be executed for the given list of MatchProfiles
|
||||
FunctionConfig config = 1;
|
||||
|
||||
// A MatchProfile that will be sent to the MatchFunction server of this FetchMatches call.
|
||||
MatchProfile profile = 2;
|
||||
// MatchProfiles for which this MatchFunction should be executed.
|
||||
repeated MatchProfile profiles = 2;
|
||||
}
|
||||
|
||||
message FetchMatchesResponse {
|
||||
// A Match generated by the user-defined MMF with the specified MatchProfiles.
|
||||
// A valid Match response will contain at least one ticket.
|
||||
// Result Match for the requested MatchProfile.
|
||||
// Note that OpenMatch will validate the proposals, a valid match should contain at least one ticket.
|
||||
Match match = 1;
|
||||
}
|
||||
|
||||
message ReleaseTicketsRequest{
|
||||
// TicketIds is a list of string representing Open Match generated Ids to be re-enabled for MMF querying
|
||||
// because they are no longer awaiting assignment from a previous match result
|
||||
repeated string ticket_ids = 1;
|
||||
}
|
||||
|
||||
message ReleaseTicketsResponse {}
|
||||
|
||||
message ReleaseAllTicketsRequest{}
|
||||
|
||||
message ReleaseAllTicketsResponse {}
|
||||
|
||||
// AssignmentGroup contains an Assignment and the Tickets to which it should be applied.
|
||||
message AssignmentGroup {
|
||||
// TicketIds is a list of strings representing Open Match generated Ids which apply to an Assignment.
|
||||
message AssignTicketsRequest {
|
||||
// List of Ticket IDs for which the Assignment is to be made.
|
||||
repeated string ticket_ids = 1;
|
||||
|
||||
// An Assignment specifies game connection related information to be associated with the TicketIds.
|
||||
// Assignment to be associated with the Ticket IDs.
|
||||
Assignment assignment = 2;
|
||||
}
|
||||
|
||||
// AssignmentFailure contains the id of the Ticket that failed the Assignment and the failure status.
|
||||
message AssignmentFailure {
|
||||
enum Cause {
|
||||
UNKNOWN = 0;
|
||||
TICKET_NOT_FOUND = 1;
|
||||
}
|
||||
message AssignTicketsResponse {}
|
||||
|
||||
string ticket_id = 1;
|
||||
Cause cause = 2;
|
||||
}
|
||||
|
||||
message AssignTicketsRequest {
|
||||
// Assignments is a list of assignment groups that contain assignment and the Tickets to which they should be applied.
|
||||
repeated AssignmentGroup assignments = 1;
|
||||
}
|
||||
|
||||
message AssignTicketsResponse {
|
||||
// Failures is a list of all the Tickets that failed assignment along with the cause of failure.
|
||||
repeated AssignmentFailure failures = 1;
|
||||
}
|
||||
|
||||
// The BackendService implements APIs to generate matches and handle ticket assignments.
|
||||
service BackendService {
|
||||
// FetchMatches triggers a MatchFunction with the specified MatchProfile and
|
||||
// returns a set of matches generated by the Match Making Function, and
|
||||
// accepted by the evaluator.
|
||||
// Tickets in matches returned by FetchMatches are moved from active to
|
||||
// pending, and will not be returned by query.
|
||||
// The service implementing the Backent API that is called to generate matches
|
||||
// and make assignments for Tickets.
|
||||
service Backend {
|
||||
// FetchMatch triggers execution of the specfied MatchFunction for each of the
|
||||
// specified MatchProfiles. Each MatchFunction execution returns a set of
|
||||
// proposals which are then evaluated to generate results. FetchMatch method
|
||||
// streams these results back to the caller.
|
||||
rpc FetchMatches(FetchMatchesRequest) returns (stream FetchMatchesResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/backendservice/matches:fetch"
|
||||
post: "/v1/backend/matches:fetch"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// AssignTickets overwrites the Assignment field of the input TicketIds.
|
||||
// AssignTickets sets the specified Assignment on the Tickets for the Ticket
|
||||
// IDs passed.
|
||||
rpc AssignTickets(AssignTicketsRequest) returns (AssignTicketsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/backendservice/tickets:assign"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// ReleaseTickets moves tickets from the pending state, to the active state.
|
||||
// This enables them to be returned by query, and find different matches.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc ReleaseTickets(ReleaseTicketsRequest) returns (ReleaseTicketsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/backendservice/tickets:release"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// ReleaseAllTickets moves all tickets from the pending state, to the active
|
||||
// state. This enables them to be returned by query, and find different
|
||||
// matches.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc ReleaseAllTickets(ReleaseAllTicketsRequest) returns (ReleaseAllTicketsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/backendservice/tickets:releaseall"
|
||||
post: "/v1/backend/tickets:assign"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
@ -13,11 +13,6 @@
|
||||
"url": "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "BackendService"
|
||||
}
|
||||
],
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
@ -29,38 +24,22 @@
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/backendservice/matches:fetch": {
|
||||
"/v1/backend/matches:fetch": {
|
||||
"post": {
|
||||
"summary": "FetchMatches triggers a MatchFunction with the specified MatchProfile and\nreturns a set of matches generated by the Match Making Function, and\naccepted by the evaluator.\nTickets in matches returned by FetchMatches are moved from active to\npending, and will not be returned by query.",
|
||||
"operationId": "BackendService_FetchMatches",
|
||||
"summary": "FetchMatch triggers execution of the specfied MatchFunction for each of the\nspecified MatchProfiles. Each MatchFunction execution returns a set of\nproposals which are then evaluated to generate results. FetchMatch method\nstreams these results back to the caller.",
|
||||
"operationId": "FetchMatches",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchFetchMatchesResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchFetchMatchesResponse"
|
||||
"$ref": "#/x-stream-definitions/openmatchFetchMatchesResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
@ -74,14 +53,14 @@
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"BackendService"
|
||||
"Backend"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/backendservice/tickets:assign": {
|
||||
"/v1/backend/tickets:assign": {
|
||||
"post": {
|
||||
"summary": "AssignTickets overwrites the Assignment field of the input TicketIds.",
|
||||
"operationId": "BackendService_AssignTickets",
|
||||
"summary": "AssignTickets sets the specified Assignment on the Tickets for the Ticket\nIDs passed.",
|
||||
"operationId": "AssignTickets",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
@ -92,15 +71,8 @@
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
@ -114,134 +86,30 @@
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"BackendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/backendservice/tickets:release": {
|
||||
"post": {
|
||||
"summary": "ReleaseTickets moves tickets from the pending state, to the active state.\nThis enables them to be returned by query, and find different matches.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "BackendService_ReleaseTickets",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchReleaseTicketsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchReleaseTicketsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"BackendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/backendservice/tickets:releaseall": {
|
||||
"post": {
|
||||
"summary": "ReleaseAllTickets moves all tickets from the pending state, to the active\nstate. This enables them to be returned by query, and find different\nmatches.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "BackendService_ReleaseAllTickets",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchReleaseAllTicketsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchReleaseAllTicketsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"BackendService"
|
||||
"Backend"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"AssignmentFailureCause": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"UNKNOWN",
|
||||
"TICKET_NOT_FOUND"
|
||||
],
|
||||
"default": "UNKNOWN"
|
||||
},
|
||||
"DoubleRangeFilterExclude": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NONE",
|
||||
"MIN",
|
||||
"MAX",
|
||||
"BOTH"
|
||||
],
|
||||
"default": "NONE",
|
||||
"title": "- NONE: No bounds should be excluded when evaluating the filter, i.e.: MIN \u003c= x \u003c= MAX\n - MIN: Only the minimum bound should be excluded when evaluating the filter, i.e.: MIN \u003c x \u003c= MAX\n - MAX: Only the maximum bound should be excluded when evaluating the filter, i.e.: MIN \u003c= x \u003c MAX\n - BOTH: Both bounds should be excluded when evaluating the filter, i.e.: MIN \u003c x \u003c MAX"
|
||||
},
|
||||
"openmatchAssignTicketsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assignments": {
|
||||
"ticket_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchAssignmentGroup"
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Assignments is a list of assignment groups that contain assignment and the Tickets to which they should be applied."
|
||||
"description": "List of Ticket IDs for which the Assignment is to be made."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "Assignment to be associated with the Ticket IDs."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchAssignTicketsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"failures": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchAssignmentFailure"
|
||||
},
|
||||
"description": "Failures is a list of all the Tickets that failed assignment along with the cause of failure."
|
||||
}
|
||||
}
|
||||
"type": "object"
|
||||
},
|
||||
"openmatchAssignment": {
|
||||
"type": "object",
|
||||
@ -250,117 +118,43 @@
|
||||
"type": "string",
|
||||
"description": "Connection information for this Assignment."
|
||||
},
|
||||
"extensions": {
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
"description": "Other details to be sent to the players."
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus",
|
||||
"description": "Error when finding an Assignment for this Ticket."
|
||||
}
|
||||
},
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket.\nOpen Match does not require or inspect any fields on assignment."
|
||||
"description": "An Assignment object represents the assignment associated with a Ticket. Open\nmatch does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchAssignmentFailure": {
|
||||
"openmatchBoolEqualsFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ticket_id": {
|
||||
"attribute": {
|
||||
"type": "string"
|
||||
},
|
||||
"cause": {
|
||||
"$ref": "#/definitions/AssignmentFailureCause"
|
||||
"value": {
|
||||
"type": "boolean",
|
||||
"format": "boolean"
|
||||
}
|
||||
},
|
||||
"description": "AssignmentFailure contains the id of the Ticket that failed the Assignment and the failure status."
|
||||
},
|
||||
"openmatchAssignmentGroup": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ticket_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "TicketIds is a list of strings representing Open Match generated Ids which apply to an Assignment."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An Assignment specifies game connection related information to be associated with the TicketIds."
|
||||
}
|
||||
},
|
||||
"description": "AssignmentGroup contains an Assignment and the Tickets to which it should be applied."
|
||||
},
|
||||
"openmatchBackfill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by\nthe Match Function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a backfill. \nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
},
|
||||
"generation": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Generation gets incremented on GameServers update operations.\nPrevents the MMF from overriding a newer version from the game server.\nDo NOT read or write to this field, it is for internal tracking, and changing the value will cause bugs."
|
||||
}
|
||||
},
|
||||
"description": "Represents a backfill entity which is used to fill partially full matches.\n\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal."
|
||||
},
|
||||
"openmatchDoubleRangeFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_arg": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket's search_fields.double_args this Filter operates on."
|
||||
},
|
||||
"max": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Maximum value."
|
||||
},
|
||||
"min": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Minimum value."
|
||||
},
|
||||
"exclude": {
|
||||
"$ref": "#/definitions/DoubleRangeFilterExclude",
|
||||
"description": "Defines the bounds to apply when filtering tickets by their search_fields.double_args value.\nBETA FEATURE WARNING: This field and the associated values are\nnot finalized and still subject to possible change or removal."
|
||||
}
|
||||
},
|
||||
"title": "Filters numerical values to only those within a range.\n double_arg: \"foo\"\n max: 10\n min: 5\nmatches:\n {\"foo\": 5}\n {\"foo\": 7.5}\n {\"foo\": 10}\ndoes not match:\n {\"foo\": 4}\n {\"foo\": 10.01}\n {\"foo\": \"7.5\"}\n {}"
|
||||
"title": "Filters boolean values.\n attribute: \"foo\"\n value: false\nmatches:\n {\"foo\": false}\ndoes not match:\n {\"foo\": true}\n {\"foo\": \"bar\"}\n {\"foo\": 1}\n {\"foo\": \"false\"}\n {\"foo\": [false]}\n {\"foo\": null}\n {}"
|
||||
},
|
||||
"openmatchFetchMatchesRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config": {
|
||||
"$ref": "#/definitions/openmatchFunctionConfig",
|
||||
"description": "A configuration for the MatchFunction server of this FetchMatches call."
|
||||
"title": "Configuration of the MatchFunction to be executed for the given list of MatchProfiles"
|
||||
},
|
||||
"profile": {
|
||||
"$ref": "#/definitions/openmatchMatchProfile",
|
||||
"description": "A MatchProfile that will be sent to the MatchFunction server of this FetchMatches call."
|
||||
"profiles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchMatchProfile"
|
||||
},
|
||||
"description": "MatchProfiles for which this MatchFunction should be executed."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -369,10 +163,30 @@
|
||||
"properties": {
|
||||
"match": {
|
||||
"$ref": "#/definitions/openmatchMatch",
|
||||
"description": "A Match generated by the user-defined MMF with the specified MatchProfiles.\nA valid Match response will contain at least one ticket."
|
||||
"description": "Result Match for the requested MatchProfile.\nNote that OpenMatch will validate the proposals, a valid match should contain at least one ticket."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchFloatRangeFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket attribute this Filter operates on."
|
||||
},
|
||||
"max": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Maximum value. Defaults to positive infinity (any value above minv)."
|
||||
},
|
||||
"min": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Minimum value. Defaults to 0."
|
||||
}
|
||||
},
|
||||
"title": "Filters numerical values to only those within a range.\n attribute: \"foo\"\n max: 10\n min: 5\nmatches:\n {\"foo\": 5}\n {\"foo\": 7.5}\n {\"foo\": 10}\ndoes not match:\n {\"foo\": 4}\n {\"foo\": 10.01}\n {\"foo\": \"7.5\"}\n {\"foo\": true}\n {\"foo\": [7.5]}\n {\"foo\": null}\n {}"
|
||||
},
|
||||
"openmatchFunctionConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -387,7 +201,7 @@
|
||||
"$ref": "#/definitions/openmatchFunctionConfigType"
|
||||
}
|
||||
},
|
||||
"title": "FunctionConfig specifies a MMF address and client type for Backend to establish connections with the MMF"
|
||||
"description": "Configuration for the Match Function to be triggered by Open Match to\ngenerate proposals."
|
||||
},
|
||||
"openmatchFunctionConfigType": {
|
||||
"type": "string",
|
||||
@ -419,23 +233,19 @@
|
||||
},
|
||||
"description": "Tickets belonging to this match."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
"rosters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchRoster"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
"title": "Set of Rosters that comprise this Match"
|
||||
},
|
||||
"backfill": {
|
||||
"$ref": "#/definitions/openmatchBackfill",
|
||||
"description": "Backfill request which contains additional information to the match\nand contains an association to a GameServer.\nBETA FEATURE WARNING: This field is not finalized and still subject\nto possible change or removal."
|
||||
},
|
||||
"allocate_gameserver": {
|
||||
"type": "boolean",
|
||||
"description": "AllocateGameServer signalise Director that Backfill is new and it should \nallocate a GameServer, this Backfill would be assigned.\nBETA FEATURE WARNING: This field is not finalized and still subject\nto possible change or removal."
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Match properties for this Match. Open Match does not interpret this field."
|
||||
}
|
||||
},
|
||||
"description": "A Match is used to represent a completed match object. It can be generated by\na MatchFunction as a proposal or can be returned by OpenMatch as a result in\nresponse to the FetchMatches call.\nWhen a match is returned by the FetchMatches call, it should contain at least\none ticket to be considered as valid."
|
||||
"description": "A Match is used to represent a completed match object. It can be generated by\na MatchFunction as a proposal or can be returned by OpenMatch as a result in\nresponse to the FetchMatches call.\nWhen a match is returned by the FetchMatches call, it should contain at least \none ticket to be considered as valid."
|
||||
},
|
||||
"openmatchMatchProfile": {
|
||||
"type": "object",
|
||||
@ -444,19 +254,23 @@
|
||||
"type": "string",
|
||||
"description": "Name of this match profile."
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Set of properties associated with this MatchProfile. (Optional)\nOpen Match does not interpret these properties but passes them through to\nthe MatchFunction."
|
||||
},
|
||||
"pools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchPool"
|
||||
},
|
||||
"description": "Set of pools to be queried when generating a match for this MatchProfile."
|
||||
"description": "Set of pools to be queried when generating a match for this MatchProfile.\nThe pool names can be used in empty Rosters to specify composition of a\nmatch."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
"rosters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchRoster"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
"description": "Set of Rosters for this match request. Could be empty Rosters used to\nindicate the composition of the generated Match or they could be partially\npre-populated Ticket list to be used in scenarios such as backfill / join\nin progress."
|
||||
}
|
||||
},
|
||||
"description": "A MatchProfile is Open Match's representation of a Match specification. It is\nused to indicate the criteria for selecting players for a match. A\nMatchProfile is the input to the API to get matches and is passed to the\nMatchFunction. It contains all the information required by the MatchFunction\nto generate match proposals."
|
||||
@ -468,145 +282,73 @@
|
||||
"type": "string",
|
||||
"description": "A developer-chosen human-readable name for this Pool."
|
||||
},
|
||||
"double_range_filters": {
|
||||
"float_range_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchDoubleRangeFilter"
|
||||
"$ref": "#/definitions/openmatchFloatRangeFilter"
|
||||
},
|
||||
"description": "Set of Filters indicating the filtering criteria. Selected tickets must\nmatch every Filter."
|
||||
"description": "Set of Filters indicating the filtering criteria. Selected players must\nmatch every Filter."
|
||||
},
|
||||
"bool_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchBoolEqualsFilter"
|
||||
}
|
||||
},
|
||||
"string_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchStringEqualsFilter"
|
||||
}
|
||||
},
|
||||
"tag_present_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTagPresentFilter"
|
||||
}
|
||||
},
|
||||
"created_before": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "If specified, only Tickets created before the specified time are selected."
|
||||
},
|
||||
"created_after": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "If specified, only Tickets created after the specified time are selected."
|
||||
}
|
||||
},
|
||||
"description": "Pool specfies a set of criteria that are used to select a subset of Tickets\nthat meet all the criteria."
|
||||
}
|
||||
},
|
||||
"openmatchReleaseAllTicketsRequest": {
|
||||
"type": "object"
|
||||
},
|
||||
"openmatchReleaseAllTicketsResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"openmatchReleaseTicketsRequest": {
|
||||
"openmatchRoster": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "A developer-chosen human-readable name for this Roster."
|
||||
},
|
||||
"ticket_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "TicketIds is a list of string representing Open Match generated Ids to be re-enabled for MMF querying\nbecause they are no longer awaiting assignment from a previous match result"
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchReleaseTicketsResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"openmatchSearchFields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"description": "Float arguments. Filterable on ranges."
|
||||
},
|
||||
"string_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "String arguments. Filterable on equality."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filterable on presence or absence of given value."
|
||||
"description": "Tickets belonging to this Roster."
|
||||
}
|
||||
},
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
"description": "A Roster is a named collection of Ticket IDs. It exists so that a Tickets\nassociated with a Match can be labelled to belong to a team, sub-team etc. It\ncan also be used to represent the current state of a Match in scenarios such\nas backfill, join-in-progress etc."
|
||||
},
|
||||
"openmatchStringEqualsFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_arg": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket's search_fields.string_args this Filter operates on."
|
||||
"attribute": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters strings exactly equaling a value.\n string_arg: \"foo\"\n value: \"bar\"\nmatches:\n {\"foo\": \"bar\"}\ndoes not match:\n {\"foo\": \"baz\"}\n {\"bar\": \"foo\"}\n {}"
|
||||
},
|
||||
"openmatchTagPresentFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters to the tag being present on the search_fields.\n tag: \"foo\"\nmatches:\n [\"foo\"]\n [\"bar\",\"foo\"]\ndoes not match:\n [\"bar\"]\n []"
|
||||
"title": "Filters strings exactly equaling a value.\n attribute: \"foo\"\n value: \"bar\"\nmatches:\n {\"foo\": \"bar\"}\ndoes not match:\n {\"foo\": \"baz\"}\n {\"foo\": true}\n {\"foo\": 5}\n {\"foo\": [\"bar\"]}\n {\"foo\": null}\n {}"
|
||||
},
|
||||
"openmatchTicket": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
"description": "The Ticket ID generated by Open Match."
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Properties contains custom info about the ticket. Top level values can be\nused in indexing and filtering to find tickets."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket,\nor whatever finalized matched state means for your use case.\nOpen Match does not require or inspect any fields on Assignment."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a ticket. \nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
"description": "Assignment associated with the Ticket."
|
||||
}
|
||||
},
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. A Ticket may represent\nan individual 'Player', a 'Group' of players, or any other concepts unique to\nyour use case. Open Match will not interpret what the Ticket represents but\njust treat it as a matchmaking unit with a set of SearchFields. Open Match\nstores the Ticket in state storage and enables an Assignment to be set on the\nTicket."
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. In order to enter\nmatchmaking using Open Match, the client should generate a Ticket, passing in\nthe properties to be associated with this Ticket. Open Match will generate an\nID for a Ticket during creation. A Ticket could be used to represent an\nindividual 'Player' or a 'Group' of players. Open Match will not interpret\nwhat the Ticket represents but just treat it as a matchmaking unit with a set\nof properties. Open Match stores the Ticket in state storage and enables an\nAssignment to be associated with this Ticket."
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
@ -623,17 +365,25 @@
|
||||
},
|
||||
"description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := ptypes.MarshalAny(foo)\n ...\n foo := \u0026pb.Foo{}\n if err := ptypes.UnmarshalAny(any, foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"
|
||||
},
|
||||
"protobufNullValue": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NULL_VALUE"
|
||||
],
|
||||
"default": "NULL_VALUE",
|
||||
"description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\n The JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value."
|
||||
},
|
||||
"rpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]."
|
||||
"description": "The status code, which should be an enum value of\n[google.rpc.Code][google.rpc.Code]."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client."
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized\nby the client."
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
@ -643,7 +393,47 @@
|
||||
"description": "A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use."
|
||||
}
|
||||
},
|
||||
"description": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors)."
|
||||
"description": "- Simple to use and understand for most users\n- Flexible enough to meet unexpected needs\n\n# Overview\n\nThe `Status` message contains three pieces of data: error code, error\nmessage, and error details. The error code should be an enum value of\n[google.rpc.Code][google.rpc.Code], but it may accept additional error codes\nif needed. The error message should be a developer-facing English message\nthat helps developers *understand* and *resolve* the error. If a localized\nuser-facing error message is needed, put the localized message in the error\ndetails or localize it in the client. The optional error details may contain\narbitrary information about the error. There is a predefined set of error\ndetail types in the package `google.rpc` that can be used for common error\nconditions.\n\n# Language mapping\n\nThe `Status` message is the logical representation of the error model, but it\nis not necessarily the actual wire format. When the `Status` message is\nexposed in different client libraries and different wire protocols, it can be\nmapped differently. For example, it will likely be mapped to some exceptions\nin Java, but more likely mapped to some error codes in C.\n\n# Other uses\n\nThe error model and the `Status` message can be used in a variety of\nenvironments, either with or without APIs, to provide a\nconsistent developer experience across different environments.\n\nExample uses of this error model include:\n\n- Partial errors. If a service needs to return partial errors to the client,\n it may embed the `Status` in the normal response to indicate the partial\n errors.\n\n- Workflow errors. A typical workflow has multiple steps. Each step may\n have a `Status` message for error reporting.\n\n- Batch operations. If a client uses batch request and batch response, the\n `Status` message should be used directly inside batch response, one for\n each error sub-response.\n\n- Asynchronous operations. If an API call embeds asynchronous operation\n results in its response, the status of those operations should be\n represented directly using the `Status` message.\n\n- Logging. If some API errors are stored in logs, the message `Status` could\n be used directly after any stripping needed for security/privacy reasons.",
|
||||
"title": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). The error model is designed to be:"
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"openmatchFetchMatchesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchFetchMatchesResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchFetchMatchesResponse"
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
|
@ -19,9 +19,9 @@ option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "api/messages.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "protoc-gen-swagger/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "Evaluator"
|
||||
version: "1.0"
|
||||
@ -52,25 +52,24 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
}
|
||||
// TODO Add annotations for security_defintiions.
|
||||
// See
|
||||
// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/a_bit_of_everything.proto
|
||||
// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/proto/examplepb/a_bit_of_everything.proto
|
||||
};
|
||||
|
||||
message EvaluateRequest {
|
||||
// A Matches proposed by the Match Function representing a candidate of the final results.
|
||||
// List of Matches to evaluate.
|
||||
Match match = 1;
|
||||
}
|
||||
|
||||
message EvaluateResponse {
|
||||
// A Match ID representing a shortlisted match returned by the evaluator as the final result.
|
||||
string match_id = 2;
|
||||
|
||||
// Deprecated fields
|
||||
reserved 1;
|
||||
// Accepted list of Matches.
|
||||
Match match = 1;
|
||||
}
|
||||
|
||||
// The Evaluator service implements APIs used to evaluate and shortlist matches proposed by MMFs.
|
||||
// The service implementing the Evaluator API that is called to evaluate
|
||||
// matches generated by MMFs and shortlist them to accepted results.
|
||||
service Evaluator {
|
||||
// Evaluate evaluates a list of proposed matches based on quality, collision status, and etc, then shortlist the matches and returns the final results.
|
||||
// Evaluate accepts a list of proposed matches, evaluates them for quality,
|
||||
// collisions etc. and returns matches that should be accepted as results.
|
||||
rpc Evaluate(stream EvaluateRequest) returns (stream EvaluateResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/evaluator/matches:evaluate"
|
||||
|
@ -13,11 +13,6 @@
|
||||
"url": "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "Evaluator"
|
||||
}
|
||||
],
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
@ -31,36 +26,20 @@
|
||||
"paths": {
|
||||
"/v1/evaluator/matches:evaluate": {
|
||||
"post": {
|
||||
"summary": "Evaluate evaluates a list of proposed matches based on quality, collision status, and etc, then shortlist the matches and returns the final results.",
|
||||
"operationId": "Evaluator_Evaluate",
|
||||
"summary": "Evaluate accepts a list of proposed matches, evaluates them for quality,\ncollisions etc. and returns matches that should be accepted as results.",
|
||||
"operationId": "Evaluate",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchEvaluateResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchEvaluateResponse"
|
||||
"$ref": "#/x-stream-definitions/openmatchEvaluateResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
@ -88,69 +67,32 @@
|
||||
"type": "string",
|
||||
"description": "Connection information for this Assignment."
|
||||
},
|
||||
"extensions": {
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
"description": "Other details to be sent to the players."
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus",
|
||||
"description": "Error when finding an Assignment for this Ticket."
|
||||
}
|
||||
},
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket.\nOpen Match does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchBackfill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by\nthe Match Function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a backfill. \nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
},
|
||||
"generation": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Generation gets incremented on GameServers update operations.\nPrevents the MMF from overriding a newer version from the game server.\nDo NOT read or write to this field, it is for internal tracking, and changing the value will cause bugs."
|
||||
}
|
||||
},
|
||||
"description": "Represents a backfill entity which is used to fill partially full matches.\n\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal."
|
||||
"description": "An Assignment object represents the assignment associated with a Ticket. Open\nmatch does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchEvaluateRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match": {
|
||||
"$ref": "#/definitions/openmatchMatch",
|
||||
"description": "A Matches proposed by the Match Function representing a candidate of the final results."
|
||||
"description": "List of Matches to evaluate."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchEvaluateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"match_id": {
|
||||
"type": "string",
|
||||
"description": "A Match ID representing a shortlisted match returned by the evaluator as the final result."
|
||||
"match": {
|
||||
"$ref": "#/definitions/openmatchMatch",
|
||||
"description": "Accepted list of Matches."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -176,88 +118,54 @@
|
||||
},
|
||||
"description": "Tickets belonging to this match."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
"rosters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchRoster"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
"title": "Set of Rosters that comprise this Match"
|
||||
},
|
||||
"backfill": {
|
||||
"$ref": "#/definitions/openmatchBackfill",
|
||||
"description": "Backfill request which contains additional information to the match\nand contains an association to a GameServer.\nBETA FEATURE WARNING: This field is not finalized and still subject\nto possible change or removal."
|
||||
},
|
||||
"allocate_gameserver": {
|
||||
"type": "boolean",
|
||||
"description": "AllocateGameServer signalise Director that Backfill is new and it should \nallocate a GameServer, this Backfill would be assigned.\nBETA FEATURE WARNING: This field is not finalized and still subject\nto possible change or removal."
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Match properties for this Match. Open Match does not interpret this field."
|
||||
}
|
||||
},
|
||||
"description": "A Match is used to represent a completed match object. It can be generated by\na MatchFunction as a proposal or can be returned by OpenMatch as a result in\nresponse to the FetchMatches call.\nWhen a match is returned by the FetchMatches call, it should contain at least\none ticket to be considered as valid."
|
||||
"description": "A Match is used to represent a completed match object. It can be generated by\na MatchFunction as a proposal or can be returned by OpenMatch as a result in\nresponse to the FetchMatches call.\nWhen a match is returned by the FetchMatches call, it should contain at least \none ticket to be considered as valid."
|
||||
},
|
||||
"openmatchSearchFields": {
|
||||
"openmatchRoster": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"description": "Float arguments. Filterable on ranges."
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "A developer-chosen human-readable name for this Roster."
|
||||
},
|
||||
"string_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "String arguments. Filterable on equality."
|
||||
},
|
||||
"tags": {
|
||||
"ticket_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filterable on presence or absence of given value."
|
||||
"description": "Tickets belonging to this Roster."
|
||||
}
|
||||
},
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
"description": "A Roster is a named collection of Ticket IDs. It exists so that a Tickets\nassociated with a Match can be labelled to belong to a team, sub-team etc. It\ncan also be used to represent the current state of a Match in scenarios such\nas backfill, join-in-progress etc."
|
||||
},
|
||||
"openmatchTicket": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
"description": "The Ticket ID generated by Open Match."
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Properties contains custom info about the ticket. Top level values can be\nused in indexing and filtering to find tickets."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket,\nor whatever finalized matched state means for your use case.\nOpen Match does not require or inspect any fields on Assignment."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a ticket. \nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
"description": "Assignment associated with the Ticket."
|
||||
}
|
||||
},
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. A Ticket may represent\nan individual 'Player', a 'Group' of players, or any other concepts unique to\nyour use case. Open Match will not interpret what the Ticket represents but\njust treat it as a matchmaking unit with a set of SearchFields. Open Match\nstores the Ticket in state storage and enables an Assignment to be set on the\nTicket."
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. In order to enter\nmatchmaking using Open Match, the client should generate a Ticket, passing in\nthe properties to be associated with this Ticket. Open Match will generate an\nID for a Ticket during creation. A Ticket could be used to represent an\nindividual 'Player' or a 'Group' of players. Open Match will not interpret\nwhat the Ticket represents but just treat it as a matchmaking unit with a set\nof properties. Open Match stores the Ticket in state storage and enables an\nAssignment to be associated with this Ticket."
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
@ -274,17 +182,25 @@
|
||||
},
|
||||
"description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := ptypes.MarshalAny(foo)\n ...\n foo := \u0026pb.Foo{}\n if err := ptypes.UnmarshalAny(any, foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"
|
||||
},
|
||||
"protobufNullValue": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NULL_VALUE"
|
||||
],
|
||||
"default": "NULL_VALUE",
|
||||
"description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\n The JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value."
|
||||
},
|
||||
"rpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]."
|
||||
"description": "The status code, which should be an enum value of\n[google.rpc.Code][google.rpc.Code]."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client."
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized\nby the client."
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
@ -294,7 +210,47 @@
|
||||
"description": "A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use."
|
||||
}
|
||||
},
|
||||
"description": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors)."
|
||||
"description": "- Simple to use and understand for most users\n- Flexible enough to meet unexpected needs\n\n# Overview\n\nThe `Status` message contains three pieces of data: error code, error\nmessage, and error details. The error code should be an enum value of\n[google.rpc.Code][google.rpc.Code], but it may accept additional error codes\nif needed. The error message should be a developer-facing English message\nthat helps developers *understand* and *resolve* the error. If a localized\nuser-facing error message is needed, put the localized message in the error\ndetails or localize it in the client. The optional error details may contain\narbitrary information about the error. There is a predefined set of error\ndetail types in the package `google.rpc` that can be used for common error\nconditions.\n\n# Language mapping\n\nThe `Status` message is the logical representation of the error model, but it\nis not necessarily the actual wire format. When the `Status` message is\nexposed in different client libraries and different wire protocols, it can be\nmapped differently. For example, it will likely be mapped to some exceptions\nin Java, but more likely mapped to some error codes in C.\n\n# Other uses\n\nThe error model and the `Status` message can be used in a variety of\nenvironments, either with or without APIs, to provide a\nconsistent developer experience across different environments.\n\nExample uses of this error model include:\n\n- Partial errors. If a service needs to return partial errors to the client,\n it may embed the `Status` in the normal response to indicate the partial\n errors.\n\n- Workflow errors. A typical workflow has multiple steps. Each step may\n have a `Status` message for error reporting.\n\n- Batch operations. If a client uses batch request and batch response, the\n `Status` message should be used directly inside batch response, one for\n each error sub-response.\n\n- Asynchronous operations. If an API call embeds asynchronous operation\n results in its response, the status of those operations should be\n represented directly using the `Status` message.\n\n- Logging. If some API errors are stored in logs, the message `Status` could\n be used directly after any stripping needed for security/privacy reasons.",
|
||||
"title": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). The error model is designed to be:"
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"openmatchEvaluateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchEvaluateResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchEvaluateResponse"
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
|
@ -19,10 +19,9 @@ option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "api/messages.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "protoc-gen-swagger/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "Frontend"
|
||||
version: "1.0"
|
||||
@ -53,170 +52,80 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
}
|
||||
// TODO Add annotations for security_defintiions.
|
||||
// See
|
||||
// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/a_bit_of_everything.proto
|
||||
// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/proto/examplepb/a_bit_of_everything.proto
|
||||
};
|
||||
|
||||
message CreateTicketRequest {
|
||||
// A Ticket object with SearchFields defined.
|
||||
// Ticket object with the properties of the Ticket to be created.
|
||||
Ticket ticket = 1;
|
||||
}
|
||||
|
||||
message CreateTicketResponse {
|
||||
// Ticket object for the created Ticket - with the ticket ID populated.
|
||||
Ticket ticket = 1;
|
||||
}
|
||||
|
||||
message DeleteTicketRequest {
|
||||
// A TicketId of a generated Ticket to be deleted.
|
||||
// Ticket ID of the Ticket to be deleted.
|
||||
string ticket_id = 1;
|
||||
}
|
||||
|
||||
message DeleteTicketResponse {}
|
||||
|
||||
message GetTicketRequest {
|
||||
// A TicketId of a generated Ticket.
|
||||
// Ticket ID of the Ticket to fetch.
|
||||
string ticket_id = 1;
|
||||
}
|
||||
|
||||
message WatchAssignmentsRequest {
|
||||
// A TicketId of a generated Ticket to get updates on.
|
||||
message GetAssignmentsRequest {
|
||||
// Ticket ID of the Ticket to get updates on.
|
||||
string ticket_id = 1;
|
||||
}
|
||||
|
||||
message WatchAssignmentsResponse {
|
||||
// An updated Assignment of the requested Ticket.
|
||||
message GetAssignmentsResponse {
|
||||
// The updated Ticket object.
|
||||
Assignment assignment = 1;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request message is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
message AcknowledgeBackfillRequest {
|
||||
// An existing ID of Backfill to acknowledge.
|
||||
string backfill_id = 1;
|
||||
|
||||
// An updated Assignment of the requested Backfill.
|
||||
Assignment assignment = 2;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request message is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
message AcknowledgeBackfillResponse {
|
||||
// The Backfill that was acknowledged.
|
||||
Backfill backfill = 1;
|
||||
|
||||
// All of the Tickets that were successfully assigned
|
||||
repeated Ticket tickets = 2;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request message is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
message CreateBackfillRequest {
|
||||
// An empty Backfill object.
|
||||
Backfill backfill = 1;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request message is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
message DeleteBackfillRequest {
|
||||
// An existing ID of Backfill to delete.
|
||||
string backfill_id = 1;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request message is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
message GetBackfillRequest {
|
||||
// An existing ID of Backfill to retrieve.
|
||||
string backfill_id = 1;
|
||||
}
|
||||
|
||||
// UpdateBackfillRequest - update searchFields, extensions and set assignment.
|
||||
//
|
||||
// BETA FEATURE WARNING: This Request message is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
message UpdateBackfillRequest {
|
||||
// A Backfill object with ID set and fields to update.
|
||||
Backfill backfill = 1;
|
||||
}
|
||||
|
||||
|
||||
// The FrontendService implements APIs to manage and query status of a Tickets.
|
||||
service FrontendService {
|
||||
// CreateTicket assigns an unique TicketId to the input Ticket and record it in state storage.
|
||||
// A ticket is considered as ready for matchmaking once it is created.
|
||||
// - If a TicketId exists in a Ticket request, an auto-generated TicketId will override this field.
|
||||
// - If SearchFields exist in a Ticket, CreateTicket will also index these fields such that one can query the ticket with query.QueryTickets function.
|
||||
rpc CreateTicket(CreateTicketRequest) returns (Ticket) {
|
||||
// The Frontend service enables creating Tickets for matchmaking and fetching
|
||||
// the status of these Tickets.
|
||||
service Frontend {
|
||||
// CreateTicket will create a new ticket, assign a Ticket ID to it and put the
|
||||
// Ticket in state storage. It will then look through the 'properties' field
|
||||
// for the attributes defined as indices the matchmakaking config. If the
|
||||
// attributes exist and are valid integers, they will be indexed. Creating a
|
||||
// ticket adds the Ticket to the pool of Tickets considered for matchmaking.
|
||||
rpc CreateTicket(CreateTicketRequest) returns (CreateTicketResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/frontendservice/tickets"
|
||||
post: "/v1/frontend/tickets"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// DeleteTicket immediately stops Open Match from using the Ticket for matchmaking and removes the Ticket from state storage.
|
||||
// The client should delete the Ticket when finished matchmaking with it.
|
||||
rpc DeleteTicket(DeleteTicketRequest) returns (google.protobuf.Empty) {
|
||||
// DeleteTicket removes the Ticket from state storage and from corresponding
|
||||
// configured indices and lazily removes the ticket from state storage.
|
||||
// Deleting a ticket immediately stops the ticket from being
|
||||
// considered for future matchmaking requests, yet when the ticket itself will be deleted
|
||||
// is undeterministic. Users may still be able to assign/get a ticket after calling DeleteTicket on it.
|
||||
rpc DeleteTicket(DeleteTicketRequest) returns (DeleteTicketResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/v1/frontendservice/tickets/{ticket_id}"
|
||||
delete: "/v1/frontend/tickets/{ticket_id}"
|
||||
};
|
||||
}
|
||||
|
||||
// GetTicket get the Ticket associated with the specified TicketId.
|
||||
// GetTicket fetches the ticket associated with the specified Ticket ID.
|
||||
rpc GetTicket(GetTicketRequest) returns (Ticket) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/frontendservice/tickets/{ticket_id}"
|
||||
get: "/v1/frontend/tickets/{ticket_id}"
|
||||
};
|
||||
}
|
||||
|
||||
// WatchAssignments stream back Assignment of the specified TicketId if it is updated.
|
||||
// - If the Assignment is not updated, GetAssignment will retry using the configured backoff strategy.
|
||||
rpc WatchAssignments(WatchAssignmentsRequest)
|
||||
returns (stream WatchAssignmentsResponse) {
|
||||
// GetAssignments streams matchmaking results from Open Match for the
|
||||
// provided Ticket ID.
|
||||
rpc GetAssignments(GetAssignmentsRequest)
|
||||
returns (stream GetAssignmentsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/frontendservice/tickets/{ticket_id}/assignments"
|
||||
};
|
||||
}
|
||||
|
||||
// AcknowledgeBackfill is used to notify OpenMatch about GameServer connection info
|
||||
// This triggers an assignment process.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc AcknowledgeBackfill(AcknowledgeBackfillRequest) returns (AcknowledgeBackfillResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/frontendservice/backfills/{backfill_id}/acknowledge"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// CreateBackfill creates a new Backfill object.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc CreateBackfill(CreateBackfillRequest) returns (Backfill) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/frontendservice/backfills"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// DeleteBackfill receives a backfill ID and deletes its resource.
|
||||
// Any tickets waiting for this backfill will be returned to the active pool, no longer pending.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc DeleteBackfill(DeleteBackfillRequest) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
delete: "/v1/frontendservice/backfills/{backfill_id}"
|
||||
};
|
||||
}
|
||||
|
||||
// GetBackfill returns a backfill object by its ID.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc GetBackfill(GetBackfillRequest) returns (Backfill) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/frontendservice/backfills/{backfill_id}"
|
||||
};
|
||||
}
|
||||
|
||||
// UpdateBackfill updates search_fields and extensions for the backfill with the provided id.
|
||||
// Any tickets waiting for this backfill will be returned to the active pool, no longer pending.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc UpdateBackfill(UpdateBackfillRequest) returns (Backfill) {
|
||||
option (google.api.http) = {
|
||||
patch: "/v1/frontendservice/backfills"
|
||||
body: "*"
|
||||
get: "/v1/frontend/tickets/{ticket_id}/assignments"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -13,11 +13,6 @@
|
||||
"url": "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "FrontendService"
|
||||
}
|
||||
],
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
@ -29,230 +24,22 @@
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/frontendservice/backfills": {
|
||||
"/v1/frontend/tickets": {
|
||||
"post": {
|
||||
"summary": "CreateBackfill creates a new Backfill object.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "FrontendService_CreateBackfill",
|
||||
"summary": "CreateTicket will create a new ticket, assign a Ticket ID to it and put the\nTicket in state storage. It will then look through the 'properties' field\nfor the attributes defined as indices the matchmakaking config. If the\nattributes exist and are valid integers, they will be indexed. Creating a\nticket adds the Ticket to the pool of Tickets considered for matchmaking.",
|
||||
"operationId": "CreateTicket",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchBackfill"
|
||||
"$ref": "#/definitions/openmatchCreateTicketResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchCreateBackfillRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"summary": "UpdateBackfill updates search_fields and extensions for the backfill with the provided id.\nAny tickets waiting for this backfill will be returned to the active pool, no longer pending.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "FrontendService_UpdateBackfill",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchBackfill"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchUpdateBackfillRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/frontendservice/backfills/{backfill_id}": {
|
||||
"get": {
|
||||
"summary": "GetBackfill returns a backfill object by its ID.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "FrontendService_GetBackfill",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchBackfill"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "backfill_id",
|
||||
"description": "An existing ID of Backfill to retrieve.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "DeleteBackfill receives a backfill ID and deletes its resource.\nAny tickets waiting for this backfill will be returned to the active pool, no longer pending.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "FrontendService_DeleteBackfill",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "backfill_id",
|
||||
"description": "An existing ID of Backfill to delete.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/frontendservice/backfills/{backfill_id}/acknowledge": {
|
||||
"post": {
|
||||
"summary": "AcknowledgeBackfill is used to notify OpenMatch about GameServer connection info\nThis triggers an assignment process.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "FrontendService_AcknowledgeBackfill",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchAcknowledgeBackfillResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "backfill_id",
|
||||
"description": "An existing ID of Backfill to acknowledge.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchAcknowledgeBackfillRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/frontendservice/tickets": {
|
||||
"post": {
|
||||
"summary": "CreateTicket assigns an unique TicketId to the input Ticket and record it in state storage.\nA ticket is considered as ready for matchmaking once it is created.\n - If a TicketId exists in a Ticket request, an auto-generated TicketId will override this field.\n - If SearchFields exist in a Ticket, CreateTicket will also index these fields such that one can query the ticket with query.QueryTickets function.",
|
||||
"operationId": "FrontendService_CreateTicket",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchTicket"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
@ -266,14 +53,14 @@
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
"Frontend"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/frontendservice/tickets/{ticket_id}": {
|
||||
"/v1/frontend/tickets/{ticket_id}": {
|
||||
"get": {
|
||||
"summary": "GetTicket get the Ticket associated with the specified TicketId.",
|
||||
"operationId": "FrontendService_GetTicket",
|
||||
"summary": "GetTicket fetches the ticket associated with the specified Ticket ID.",
|
||||
"operationId": "GetTicket",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
@ -284,149 +71,88 @@
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ticket_id",
|
||||
"description": "A TicketId of a generated Ticket.",
|
||||
"description": "Ticket ID of the Ticket to fetch.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
"Frontend"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"summary": "DeleteTicket immediately stops Open Match from using the Ticket for matchmaking and removes the Ticket from state storage.\nThe client should delete the Ticket when finished matchmaking with it.",
|
||||
"operationId": "FrontendService_DeleteTicket",
|
||||
"summary": "DeleteTicket removes the Ticket from state storage and from corresponding\nconfigured indices and lazily removes the ticket from state storage.\nDeleting a ticket immediately stops the ticket from being\nconsidered for future matchmaking requests, yet when the ticket itself will be deleted\nis undeterministic. Users may still be able to assign/get a ticket after calling DeleteTicket on it.",
|
||||
"operationId": "DeleteTicket",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"properties": {}
|
||||
"$ref": "#/definitions/openmatchDeleteTicketResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ticket_id",
|
||||
"description": "A TicketId of a generated Ticket to be deleted.",
|
||||
"description": "Ticket ID of the Ticket to be deleted.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
"Frontend"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/frontendservice/tickets/{ticket_id}/assignments": {
|
||||
"/v1/frontend/tickets/{ticket_id}/assignments": {
|
||||
"get": {
|
||||
"summary": "WatchAssignments stream back Assignment of the specified TicketId if it is updated.\n - If the Assignment is not updated, GetAssignment will retry using the configured backoff strategy.",
|
||||
"operationId": "FrontendService_WatchAssignments",
|
||||
"summary": "GetAssignments streams matchmaking results from Open Match for the\nprovided Ticket ID.",
|
||||
"operationId": "GetAssignments",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchWatchAssignmentsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchWatchAssignmentsResponse"
|
||||
"$ref": "#/x-stream-definitions/openmatchGetAssignmentsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ticket_id",
|
||||
"description": "A TicketId of a generated Ticket to get updates on.",
|
||||
"description": "Ticket ID of the Ticket to get updates on.",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"FrontendService"
|
||||
"Frontend"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"openmatchAcknowledgeBackfillRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backfill_id": {
|
||||
"type": "string",
|
||||
"description": "An existing ID of Backfill to acknowledge."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An updated Assignment of the requested Backfill."
|
||||
}
|
||||
},
|
||||
"description": "BETA FEATURE WARNING: This Request message is not finalized and still subject\nto possible change or removal."
|
||||
},
|
||||
"openmatchAcknowledgeBackfillResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backfill": {
|
||||
"$ref": "#/definitions/openmatchBackfill",
|
||||
"description": "The Backfill that was acknowledged."
|
||||
},
|
||||
"tickets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTicket"
|
||||
},
|
||||
"title": "All of the Tickets that were successfully assigned"
|
||||
}
|
||||
},
|
||||
"description": "BETA FEATURE WARNING: This Request message is not finalized and still subject\nto possible change or removal."
|
||||
},
|
||||
"openmatchAssignment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -434,156 +160,64 @@
|
||||
"type": "string",
|
||||
"description": "Connection information for this Assignment."
|
||||
},
|
||||
"extensions": {
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
"description": "Other details to be sent to the players."
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus",
|
||||
"description": "Error when finding an Assignment for this Ticket."
|
||||
}
|
||||
},
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket.\nOpen Match does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchBackfill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by\nthe Match Function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a backfill. \nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
},
|
||||
"generation": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Generation gets incremented on GameServers update operations.\nPrevents the MMF from overriding a newer version from the game server.\nDo NOT read or write to this field, it is for internal tracking, and changing the value will cause bugs."
|
||||
}
|
||||
},
|
||||
"description": "Represents a backfill entity which is used to fill partially full matches.\n\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal."
|
||||
},
|
||||
"openmatchCreateBackfillRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backfill": {
|
||||
"$ref": "#/definitions/openmatchBackfill",
|
||||
"description": "An empty Backfill object."
|
||||
}
|
||||
},
|
||||
"description": "BETA FEATURE WARNING: This Request message is not finalized and still subject\nto possible change or removal."
|
||||
"description": "An Assignment object represents the assignment associated with a Ticket. Open\nmatch does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchCreateTicketRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ticket": {
|
||||
"$ref": "#/definitions/openmatchTicket",
|
||||
"description": "A Ticket object with SearchFields defined."
|
||||
"description": "Ticket object with the properties of the Ticket to be created."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchSearchFields": {
|
||||
"openmatchCreateTicketResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"description": "Float arguments. Filterable on ranges."
|
||||
},
|
||||
"string_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "String arguments. Filterable on equality."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filterable on presence or absence of given value."
|
||||
"ticket": {
|
||||
"$ref": "#/definitions/openmatchTicket",
|
||||
"description": "Ticket object for the created Ticket - with the ticket ID populated."
|
||||
}
|
||||
},
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
}
|
||||
},
|
||||
"openmatchDeleteTicketResponse": {
|
||||
"type": "object"
|
||||
},
|
||||
"openmatchGetAssignmentsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "The updated Ticket object."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchTicket": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
"description": "The Ticket ID generated by Open Match."
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Properties contains custom info about the ticket. Top level values can be\nused in indexing and filtering to find tickets."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket,\nor whatever finalized matched state means for your use case.\nOpen Match does not require or inspect any fields on Assignment."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a ticket. \nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
"description": "Assignment associated with the Ticket."
|
||||
}
|
||||
},
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. A Ticket may represent\nan individual 'Player', a 'Group' of players, or any other concepts unique to\nyour use case. Open Match will not interpret what the Ticket represents but\njust treat it as a matchmaking unit with a set of SearchFields. Open Match\nstores the Ticket in state storage and enables an Assignment to be set on the\nTicket."
|
||||
},
|
||||
"openmatchUpdateBackfillRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backfill": {
|
||||
"$ref": "#/definitions/openmatchBackfill",
|
||||
"description": "A Backfill object with ID set and fields to update."
|
||||
}
|
||||
},
|
||||
"description": "UpdateBackfillRequest - update searchFields, extensions and set assignment.\n\nBETA FEATURE WARNING: This Request message is not finalized and still subject\nto possible change or removal."
|
||||
},
|
||||
"openmatchWatchAssignmentsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An updated Assignment of the requested Ticket."
|
||||
}
|
||||
}
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. In order to enter\nmatchmaking using Open Match, the client should generate a Ticket, passing in\nthe properties to be associated with this Ticket. Open Match will generate an\nID for a Ticket during creation. A Ticket could be used to represent an\nindividual 'Player' or a 'Group' of players. Open Match will not interpret\nwhat the Ticket represents but just treat it as a matchmaking unit with a set\nof properties. Open Match stores the Ticket in state storage and enables an\nAssignment to be associated with this Ticket."
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
@ -600,17 +234,25 @@
|
||||
},
|
||||
"description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := ptypes.MarshalAny(foo)\n ...\n foo := \u0026pb.Foo{}\n if err := ptypes.UnmarshalAny(any, foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"
|
||||
},
|
||||
"protobufNullValue": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NULL_VALUE"
|
||||
],
|
||||
"default": "NULL_VALUE",
|
||||
"description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\n The JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value."
|
||||
},
|
||||
"rpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]."
|
||||
"description": "The status code, which should be an enum value of\n[google.rpc.Code][google.rpc.Code]."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client."
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized\nby the client."
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
@ -620,7 +262,47 @@
|
||||
"description": "A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use."
|
||||
}
|
||||
},
|
||||
"description": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors)."
|
||||
"description": "- Simple to use and understand for most users\n- Flexible enough to meet unexpected needs\n\n# Overview\n\nThe `Status` message contains three pieces of data: error code, error\nmessage, and error details. The error code should be an enum value of\n[google.rpc.Code][google.rpc.Code], but it may accept additional error codes\nif needed. The error message should be a developer-facing English message\nthat helps developers *understand* and *resolve* the error. If a localized\nuser-facing error message is needed, put the localized message in the error\ndetails or localize it in the client. The optional error details may contain\narbitrary information about the error. There is a predefined set of error\ndetail types in the package `google.rpc` that can be used for common error\nconditions.\n\n# Language mapping\n\nThe `Status` message is the logical representation of the error model, but it\nis not necessarily the actual wire format. When the `Status` message is\nexposed in different client libraries and different wire protocols, it can be\nmapped differently. For example, it will likely be mapped to some exceptions\nin Java, but more likely mapped to some error codes in C.\n\n# Other uses\n\nThe error model and the `Status` message can be used in a variety of\nenvironments, either with or without APIs, to provide a\nconsistent developer experience across different environments.\n\nExample uses of this error model include:\n\n- Partial errors. If a service needs to return partial errors to the client,\n it may embed the `Status` in the normal response to indicate the partial\n errors.\n\n- Workflow errors. A typical workflow has multiple steps. Each step may\n have a `Status` message for error reporting.\n\n- Batch operations. If a client uses batch request and batch response, the\n `Status` message should be used directly inside batch response, one for\n each error sub-response.\n\n- Asynchronous operations. If an API call embeds asynchronous operation\n results in its response, the status of those operations should be\n represented directly using the `Status` message.\n\n- Logging. If some API errors are stored in logs, the message `Status` could\n be used directly after any stripping needed for security/privacy reasons.",
|
||||
"title": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). The error model is designed to be:"
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"openmatchGetAssignmentsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchGetAssignmentsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchGetAssignmentsResponse"
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
|
@ -19,9 +19,9 @@ option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "api/messages.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "protoc-gen-swagger/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "Match Function"
|
||||
version: "1.0"
|
||||
@ -56,22 +56,22 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
};
|
||||
|
||||
message RunRequest {
|
||||
// A MatchProfile defines constraints of Tickets in a Match and shapes the Match proposed by the MatchFunction.
|
||||
// The MatchProfile that describes the Match that this MatchFunction needs to
|
||||
// generate proposals for.
|
||||
MatchProfile profile = 1;
|
||||
}
|
||||
|
||||
message RunResponse {
|
||||
// A Proposal represents a Match candidate that satifies the constraints defined in the input Profile.
|
||||
// A valid Proposal response will contain at least one ticket.
|
||||
// The proposal generated by this MatchFunction Run.
|
||||
// Note that OpenMatch will validate the proposals, a valid match should contain at least one ticket.
|
||||
Match proposal = 1;
|
||||
}
|
||||
|
||||
// The MatchFunction service implements APIs to run user-defined matchmaking logics.
|
||||
// This proto defines the API for running Match Functions as long-lived,
|
||||
// 'serving' functions.
|
||||
service MatchFunction {
|
||||
// DO NOT CALL THIS FUNCTION MANUALLY. USE backend.FetchMatches INSTEAD.
|
||||
// Run pulls Tickets that satisfy Profile constraints from QueryService,
|
||||
// runs matchmaking logic against them, then constructs and streams back
|
||||
// match candidates to the Backend service.
|
||||
// This is the function that is executed when by the Open Match backend to
|
||||
// generate Match proposals.
|
||||
rpc Run(RunRequest) returns (stream RunResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/matchfunction:run"
|
||||
|
@ -13,11 +13,6 @@
|
||||
"url": "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "MatchFunction"
|
||||
}
|
||||
],
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
@ -31,36 +26,20 @@
|
||||
"paths": {
|
||||
"/v1/matchfunction:run": {
|
||||
"post": {
|
||||
"summary": "DO NOT CALL THIS FUNCTION MANUALLY. USE backend.FetchMatches INSTEAD.\nRun pulls Tickets that satisfy Profile constraints from QueryService,\nruns matchmaking logic against them, then constructs and streams back\nmatch candidates to the Backend service.",
|
||||
"operationId": "MatchFunction_Run",
|
||||
"summary": "This is the function that is executed when by the Open Match backend to\ngenerate Match proposals.",
|
||||
"operationId": "Run",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchRunResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchRunResponse"
|
||||
"$ref": "#/x-stream-definitions/openmatchRunResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
@ -80,17 +59,6 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"DoubleRangeFilterExclude": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NONE",
|
||||
"MIN",
|
||||
"MAX",
|
||||
"BOTH"
|
||||
],
|
||||
"default": "NONE",
|
||||
"title": "- NONE: No bounds should be excluded when evaluating the filter, i.e.: MIN \u003c= x \u003c= MAX\n - MIN: Only the minimum bound should be excluded when evaluating the filter, i.e.: MIN \u003c x \u003c= MAX\n - MAX: Only the maximum bound should be excluded when evaluating the filter, i.e.: MIN \u003c= x \u003c MAX\n - BOTH: Both bounds should be excluded when evaluating the filter, i.e.: MIN \u003c x \u003c MAX"
|
||||
},
|
||||
"openmatchAssignment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -98,77 +66,49 @@
|
||||
"type": "string",
|
||||
"description": "Connection information for this Assignment."
|
||||
},
|
||||
"extensions": {
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
"description": "Other details to be sent to the players."
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus",
|
||||
"description": "Error when finding an Assignment for this Ticket."
|
||||
}
|
||||
},
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket.\nOpen Match does not require or inspect any fields on assignment."
|
||||
"description": "An Assignment object represents the assignment associated with a Ticket. Open\nmatch does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchBackfill": {
|
||||
"openmatchBoolEqualsFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
"attribute": {
|
||||
"type": "string"
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by\nthe Match Function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a backfill. \nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
},
|
||||
"generation": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Generation gets incremented on GameServers update operations.\nPrevents the MMF from overriding a newer version from the game server.\nDo NOT read or write to this field, it is for internal tracking, and changing the value will cause bugs."
|
||||
"value": {
|
||||
"type": "boolean",
|
||||
"format": "boolean"
|
||||
}
|
||||
},
|
||||
"description": "Represents a backfill entity which is used to fill partially full matches.\n\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal."
|
||||
"title": "Filters boolean values.\n attribute: \"foo\"\n value: false\nmatches:\n {\"foo\": false}\ndoes not match:\n {\"foo\": true}\n {\"foo\": \"bar\"}\n {\"foo\": 1}\n {\"foo\": \"false\"}\n {\"foo\": [false]}\n {\"foo\": null}\n {}"
|
||||
},
|
||||
"openmatchDoubleRangeFilter": {
|
||||
"openmatchFloatRangeFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_arg": {
|
||||
"attribute": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket's search_fields.double_args this Filter operates on."
|
||||
"description": "Name of the ticket attribute this Filter operates on."
|
||||
},
|
||||
"max": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Maximum value."
|
||||
"description": "Maximum value. Defaults to positive infinity (any value above minv)."
|
||||
},
|
||||
"min": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Minimum value."
|
||||
},
|
||||
"exclude": {
|
||||
"$ref": "#/definitions/DoubleRangeFilterExclude",
|
||||
"description": "Defines the bounds to apply when filtering tickets by their search_fields.double_args value.\nBETA FEATURE WARNING: This field and the associated values are\nnot finalized and still subject to possible change or removal."
|
||||
"description": "Minimum value. Defaults to 0."
|
||||
}
|
||||
},
|
||||
"title": "Filters numerical values to only those within a range.\n double_arg: \"foo\"\n max: 10\n min: 5\nmatches:\n {\"foo\": 5}\n {\"foo\": 7.5}\n {\"foo\": 10}\ndoes not match:\n {\"foo\": 4}\n {\"foo\": 10.01}\n {\"foo\": \"7.5\"}\n {}"
|
||||
"title": "Filters numerical values to only those within a range.\n attribute: \"foo\"\n max: 10\n min: 5\nmatches:\n {\"foo\": 5}\n {\"foo\": 7.5}\n {\"foo\": 10}\ndoes not match:\n {\"foo\": 4}\n {\"foo\": 10.01}\n {\"foo\": \"7.5\"}\n {\"foo\": true}\n {\"foo\": [7.5]}\n {\"foo\": null}\n {}"
|
||||
},
|
||||
"openmatchMatch": {
|
||||
"type": "object",
|
||||
@ -192,23 +132,19 @@
|
||||
},
|
||||
"description": "Tickets belonging to this match."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
"rosters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchRoster"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
"title": "Set of Rosters that comprise this Match"
|
||||
},
|
||||
"backfill": {
|
||||
"$ref": "#/definitions/openmatchBackfill",
|
||||
"description": "Backfill request which contains additional information to the match\nand contains an association to a GameServer.\nBETA FEATURE WARNING: This field is not finalized and still subject\nto possible change or removal."
|
||||
},
|
||||
"allocate_gameserver": {
|
||||
"type": "boolean",
|
||||
"description": "AllocateGameServer signalise Director that Backfill is new and it should \nallocate a GameServer, this Backfill would be assigned.\nBETA FEATURE WARNING: This field is not finalized and still subject\nto possible change or removal."
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Match properties for this Match. Open Match does not interpret this field."
|
||||
}
|
||||
},
|
||||
"description": "A Match is used to represent a completed match object. It can be generated by\na MatchFunction as a proposal or can be returned by OpenMatch as a result in\nresponse to the FetchMatches call.\nWhen a match is returned by the FetchMatches call, it should contain at least\none ticket to be considered as valid."
|
||||
"description": "A Match is used to represent a completed match object. It can be generated by\na MatchFunction as a proposal or can be returned by OpenMatch as a result in\nresponse to the FetchMatches call.\nWhen a match is returned by the FetchMatches call, it should contain at least \none ticket to be considered as valid."
|
||||
},
|
||||
"openmatchMatchProfile": {
|
||||
"type": "object",
|
||||
@ -217,19 +153,23 @@
|
||||
"type": "string",
|
||||
"description": "Name of this match profile."
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Set of properties associated with this MatchProfile. (Optional)\nOpen Match does not interpret these properties but passes them through to\nthe MatchFunction."
|
||||
},
|
||||
"pools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchPool"
|
||||
},
|
||||
"description": "Set of pools to be queried when generating a match for this MatchProfile."
|
||||
"description": "Set of pools to be queried when generating a match for this MatchProfile.\nThe pool names can be used in empty Rosters to specify composition of a\nmatch."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
"rosters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchRoster"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
"description": "Set of Rosters for this match request. Could be empty Rosters used to\nindicate the composition of the generated Match or they could be partially\npre-populated Ticket list to be used in scenarios such as backfill / join\nin progress."
|
||||
}
|
||||
},
|
||||
"description": "A MatchProfile is Open Match's representation of a Match specification. It is\nused to indicate the criteria for selecting players for a match. A\nMatchProfile is the input to the API to get matches and is passed to the\nMatchFunction. It contains all the information required by the MatchFunction\nto generate match proposals."
|
||||
@ -241,44 +181,50 @@
|
||||
"type": "string",
|
||||
"description": "A developer-chosen human-readable name for this Pool."
|
||||
},
|
||||
"double_range_filters": {
|
||||
"float_range_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchDoubleRangeFilter"
|
||||
"$ref": "#/definitions/openmatchFloatRangeFilter"
|
||||
},
|
||||
"description": "Set of Filters indicating the filtering criteria. Selected tickets must\nmatch every Filter."
|
||||
"description": "Set of Filters indicating the filtering criteria. Selected players must\nmatch every Filter."
|
||||
},
|
||||
"bool_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchBoolEqualsFilter"
|
||||
}
|
||||
},
|
||||
"string_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchStringEqualsFilter"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchRoster": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "A developer-chosen human-readable name for this Roster."
|
||||
},
|
||||
"tag_present_filters": {
|
||||
"ticket_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTagPresentFilter"
|
||||
}
|
||||
},
|
||||
"created_before": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "If specified, only Tickets created before the specified time are selected."
|
||||
},
|
||||
"created_after": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "If specified, only Tickets created after the specified time are selected."
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Tickets belonging to this Roster."
|
||||
}
|
||||
},
|
||||
"description": "Pool specfies a set of criteria that are used to select a subset of Tickets\nthat meet all the criteria."
|
||||
"description": "A Roster is a named collection of Ticket IDs. It exists so that a Tickets\nassociated with a Match can be labelled to belong to a team, sub-team etc. It\ncan also be used to represent the current state of a Match in scenarios such\nas backfill, join-in-progress etc."
|
||||
},
|
||||
"openmatchRunRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"profile": {
|
||||
"$ref": "#/definitions/openmatchMatchProfile",
|
||||
"description": "A MatchProfile defines constraints of Tickets in a Match and shapes the Match proposed by the MatchFunction."
|
||||
"description": "The MatchProfile that describes the Match that this MatchFunction needs to\ngenerate proposals for."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -287,96 +233,39 @@
|
||||
"properties": {
|
||||
"proposal": {
|
||||
"$ref": "#/definitions/openmatchMatch",
|
||||
"description": "A Proposal represents a Match candidate that satifies the constraints defined in the input Profile.\nA valid Proposal response will contain at least one ticket."
|
||||
"description": "The proposal generated by this MatchFunction Run.\nNote that OpenMatch will validate the proposals, a valid match should contain at least one ticket."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchSearchFields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"description": "Float arguments. Filterable on ranges."
|
||||
},
|
||||
"string_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "String arguments. Filterable on equality."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filterable on presence or absence of given value."
|
||||
}
|
||||
},
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"openmatchStringEqualsFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_arg": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket's search_fields.string_args this Filter operates on."
|
||||
"attribute": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters strings exactly equaling a value.\n string_arg: \"foo\"\n value: \"bar\"\nmatches:\n {\"foo\": \"bar\"}\ndoes not match:\n {\"foo\": \"baz\"}\n {\"bar\": \"foo\"}\n {}"
|
||||
},
|
||||
"openmatchTagPresentFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters to the tag being present on the search_fields.\n tag: \"foo\"\nmatches:\n [\"foo\"]\n [\"bar\",\"foo\"]\ndoes not match:\n [\"bar\"]\n []"
|
||||
"title": "Filters strings exactly equaling a value.\n attribute: \"foo\"\n value: \"bar\"\nmatches:\n {\"foo\": \"bar\"}\ndoes not match:\n {\"foo\": \"baz\"}\n {\"foo\": true}\n {\"foo\": 5}\n {\"foo\": [\"bar\"]}\n {\"foo\": null}\n {}"
|
||||
},
|
||||
"openmatchTicket": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
"description": "The Ticket ID generated by Open Match."
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Properties contains custom info about the ticket. Top level values can be\nused in indexing and filtering to find tickets."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket,\nor whatever finalized matched state means for your use case.\nOpen Match does not require or inspect any fields on Assignment."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a ticket. \nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
"description": "Assignment associated with the Ticket."
|
||||
}
|
||||
},
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. A Ticket may represent\nan individual 'Player', a 'Group' of players, or any other concepts unique to\nyour use case. Open Match will not interpret what the Ticket represents but\njust treat it as a matchmaking unit with a set of SearchFields. Open Match\nstores the Ticket in state storage and enables an Assignment to be set on the\nTicket."
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. In order to enter\nmatchmaking using Open Match, the client should generate a Ticket, passing in\nthe properties to be associated with this Ticket. Open Match will generate an\nID for a Ticket during creation. A Ticket could be used to represent an\nindividual 'Player' or a 'Group' of players. Open Match will not interpret\nwhat the Ticket represents but just treat it as a matchmaking unit with a set\nof properties. Open Match stores the Ticket in state storage and enables an\nAssignment to be associated with this Ticket."
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
@ -393,17 +282,25 @@
|
||||
},
|
||||
"description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := ptypes.MarshalAny(foo)\n ...\n foo := \u0026pb.Foo{}\n if err := ptypes.UnmarshalAny(any, foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"
|
||||
},
|
||||
"protobufNullValue": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NULL_VALUE"
|
||||
],
|
||||
"default": "NULL_VALUE",
|
||||
"description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\n The JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value."
|
||||
},
|
||||
"rpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]."
|
||||
"description": "The status code, which should be an enum value of\n[google.rpc.Code][google.rpc.Code]."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client."
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized\nby the client."
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
@ -413,7 +310,47 @@
|
||||
"description": "A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use."
|
||||
}
|
||||
},
|
||||
"description": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors)."
|
||||
"description": "- Simple to use and understand for most users\n- Flexible enough to meet unexpected needs\n\n# Overview\n\nThe `Status` message contains three pieces of data: error code, error\nmessage, and error details. The error code should be an enum value of\n[google.rpc.Code][google.rpc.Code], but it may accept additional error codes\nif needed. The error message should be a developer-facing English message\nthat helps developers *understand* and *resolve* the error. If a localized\nuser-facing error message is needed, put the localized message in the error\ndetails or localize it in the client. The optional error details may contain\narbitrary information about the error. There is a predefined set of error\ndetail types in the package `google.rpc` that can be used for common error\nconditions.\n\n# Language mapping\n\nThe `Status` message is the logical representation of the error model, but it\nis not necessarily the actual wire format. When the `Status` message is\nexposed in different client libraries and different wire protocols, it can be\nmapped differently. For example, it will likely be mapped to some exceptions\nin Java, but more likely mapped to some error codes in C.\n\n# Other uses\n\nThe error model and the `Status` message can be used in a variety of\nenvironments, either with or without APIs, to provide a\nconsistent developer experience across different environments.\n\nExample uses of this error model include:\n\n- Partial errors. If a service needs to return partial errors to the client,\n it may embed the `Status` in the normal response to indicate the partial\n errors.\n\n- Workflow errors. A typical workflow has multiple steps. Each step may\n have a `Status` message for error reporting.\n\n- Batch operations. If a client uses batch request and batch response, the\n `Status` message should be used directly inside batch response, one for\n each error sub-response.\n\n- Asynchronous operations. If an API call embeds asynchronous operation\n results in its response, the status of those operations should be\n represented directly using the `Status` message.\n\n- Logging. If some API errors are stored in logs, the message `Status` could\n be used directly after any stripping needed for security/privacy reasons.",
|
||||
"title": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). The error model is designed to be:"
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"openmatchRunResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchRunResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchRunResponse"
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
|
@ -18,76 +18,43 @@ option go_package = "open-match.dev/open-match/pkg/pb";
|
||||
option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "google/rpc/status.proto";
|
||||
import "google/protobuf/any.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/struct.proto";
|
||||
|
||||
// A Ticket is a basic matchmaking entity in Open Match. A Ticket may represent
|
||||
// an individual 'Player', a 'Group' of players, or any other concepts unique to
|
||||
// your use case. Open Match will not interpret what the Ticket represents but
|
||||
// just treat it as a matchmaking unit with a set of SearchFields. Open Match
|
||||
// stores the Ticket in state storage and enables an Assignment to be set on the
|
||||
// Ticket.
|
||||
// A Ticket is a basic matchmaking entity in Open Match. In order to enter
|
||||
// matchmaking using Open Match, the client should generate a Ticket, passing in
|
||||
// the properties to be associated with this Ticket. Open Match will generate an
|
||||
// ID for a Ticket during creation. A Ticket could be used to represent an
|
||||
// individual 'Player' or a 'Group' of players. Open Match will not interpret
|
||||
// what the Ticket represents but just treat it as a matchmaking unit with a set
|
||||
// of properties. Open Match stores the Ticket in state storage and enables an
|
||||
// Assignment to be associated with this Ticket.
|
||||
message Ticket {
|
||||
// Id represents an auto-generated Id issued by Open Match.
|
||||
// The Ticket ID generated by Open Match.
|
||||
string id = 1;
|
||||
|
||||
// An Assignment represents a game server assignment associated with a Ticket,
|
||||
// or whatever finalized matched state means for your use case.
|
||||
// Open Match does not require or inspect any fields on Assignment.
|
||||
// Properties contains custom info about the ticket. Top level values can be
|
||||
// used in indexing and filtering to find tickets.
|
||||
google.protobuf.Struct properties = 2;
|
||||
|
||||
// Assignment associated with the Ticket.
|
||||
Assignment assignment = 3;
|
||||
|
||||
// Search fields are the fields which Open Match is aware of, and can be used
|
||||
// when specifying filters.
|
||||
SearchFields search_fields = 4;
|
||||
|
||||
// Customized information not inspected by Open Match, to be used by the match
|
||||
// making function, evaluator, and components making calls to Open Match.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> extensions = 5;
|
||||
|
||||
// Customized information not inspected by Open Match, to be kept persistent
|
||||
// throughout the life-cycle of a ticket.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> persistent_field = 6;
|
||||
|
||||
// Create time is the time the Ticket was created. It is populated by Open
|
||||
// Match at the time of Ticket creation.
|
||||
google.protobuf.Timestamp create_time = 7;
|
||||
|
||||
// Deprecated fields.
|
||||
reserved 2;
|
||||
}
|
||||
|
||||
// Search fields are the fields which Open Match is aware of, and can be used
|
||||
// when specifying filters.
|
||||
message SearchFields {
|
||||
// Float arguments. Filterable on ranges.
|
||||
map<string, double> double_args = 1;
|
||||
|
||||
// String arguments. Filterable on equality.
|
||||
map<string, string> string_args = 2;
|
||||
|
||||
// Filterable on presence or absence of given value.
|
||||
repeated string tags = 3;
|
||||
}
|
||||
|
||||
// An Assignment represents a game server assignment associated with a Ticket.
|
||||
// Open Match does not require or inspect any fields on assignment.
|
||||
// An Assignment object represents the assignment associated with a Ticket. Open
|
||||
// match does not require or inspect any fields on assignment.
|
||||
message Assignment {
|
||||
// Connection information for this Assignment.
|
||||
string connection = 1;
|
||||
|
||||
// Customized information not inspected by Open Match, to be used by the match
|
||||
// making function, evaluator, and components making calls to Open Match.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> extensions = 4;
|
||||
// Other details to be sent to the players.
|
||||
google.protobuf.Struct properties = 2;
|
||||
|
||||
// Deprecated fields.
|
||||
reserved 2, 3;
|
||||
// Error when finding an Assignment for this Ticket.
|
||||
google.rpc.Status error = 3;
|
||||
}
|
||||
|
||||
// Filters numerical values to only those within a range.
|
||||
// double_arg: "foo"
|
||||
// attribute: "foo"
|
||||
// max: 10
|
||||
// min: 5
|
||||
// matches:
|
||||
@ -98,87 +65,81 @@ message Assignment {
|
||||
// {"foo": 4}
|
||||
// {"foo": 10.01}
|
||||
// {"foo": "7.5"}
|
||||
// {"foo": true}
|
||||
// {"foo": [7.5]}
|
||||
// {"foo": null}
|
||||
// {}
|
||||
message DoubleRangeFilter {
|
||||
// Name of the ticket's search_fields.double_args this Filter operates on.
|
||||
string double_arg = 1;
|
||||
message FloatRangeFilter {
|
||||
// Name of the ticket attribute this Filter operates on.
|
||||
string attribute = 1;
|
||||
|
||||
// Maximum value.
|
||||
// Maximum value. Defaults to positive infinity (any value above minv).
|
||||
double max = 2;
|
||||
|
||||
// Minimum value.
|
||||
// Minimum value. Defaults to 0.
|
||||
double min = 3;
|
||||
}
|
||||
|
||||
enum Exclude {
|
||||
// No bounds should be excluded when evaluating the filter, i.e.: MIN <= x <= MAX
|
||||
NONE = 0;
|
||||
// Filters boolean values.
|
||||
// attribute: "foo"
|
||||
// value: false
|
||||
// matches:
|
||||
// {"foo": false}
|
||||
// does not match:
|
||||
// {"foo": true}
|
||||
// {"foo": "bar"}
|
||||
// {"foo": 1}
|
||||
// {"foo": "false"}
|
||||
// {"foo": [false]}
|
||||
// {"foo": null}
|
||||
// {}
|
||||
message BoolEqualsFilter {
|
||||
string attribute = 1;
|
||||
|
||||
// Only the minimum bound should be excluded when evaluating the filter, i.e.: MIN < x <= MAX
|
||||
MIN = 1;
|
||||
|
||||
// Only the maximum bound should be excluded when evaluating the filter, i.e.: MIN <= x < MAX
|
||||
MAX = 2;
|
||||
|
||||
// Both bounds should be excluded when evaluating the filter, i.e.: MIN < x < MAX
|
||||
BOTH = 3;
|
||||
}
|
||||
|
||||
// Defines the bounds to apply when filtering tickets by their search_fields.double_args value.
|
||||
// BETA FEATURE WARNING: This field and the associated values are
|
||||
// not finalized and still subject to possible change or removal.
|
||||
Exclude exclude = 4;
|
||||
bool value = 2;
|
||||
}
|
||||
|
||||
// Filters strings exactly equaling a value.
|
||||
// string_arg: "foo"
|
||||
// attribute: "foo"
|
||||
// value: "bar"
|
||||
// matches:
|
||||
// {"foo": "bar"}
|
||||
// does not match:
|
||||
// {"foo": "baz"}
|
||||
// {"bar": "foo"}
|
||||
// {"foo": true}
|
||||
// {"foo": 5}
|
||||
// {"foo": ["bar"]}
|
||||
// {"foo": null}
|
||||
// {}
|
||||
message StringEqualsFilter {
|
||||
// Name of the ticket's search_fields.string_args this Filter operates on.
|
||||
string string_arg = 1;
|
||||
string attribute = 1;
|
||||
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
// Filters to the tag being present on the search_fields.
|
||||
// tag: "foo"
|
||||
// matches:
|
||||
// ["foo"]
|
||||
// ["bar","foo"]
|
||||
// does not match:
|
||||
// ["bar"]
|
||||
// []
|
||||
message TagPresentFilter {
|
||||
string tag = 1;
|
||||
}
|
||||
|
||||
// Pool specfies a set of criteria that are used to select a subset of Tickets
|
||||
// that meet all the criteria.
|
||||
message Pool {
|
||||
// A developer-chosen human-readable name for this Pool.
|
||||
string name = 1;
|
||||
|
||||
// Set of Filters indicating the filtering criteria. Selected tickets must
|
||||
// Set of Filters indicating the filtering criteria. Selected players must
|
||||
// match every Filter.
|
||||
repeated DoubleRangeFilter double_range_filters = 2;
|
||||
repeated FloatRangeFilter float_range_filters = 2;
|
||||
|
||||
repeated BoolEqualsFilter bool_equals_filters = 3;
|
||||
|
||||
repeated StringEqualsFilter string_equals_filters = 4;
|
||||
}
|
||||
|
||||
repeated TagPresentFilter tag_present_filters = 5;
|
||||
// A Roster is a named collection of Ticket IDs. It exists so that a Tickets
|
||||
// associated with a Match can be labelled to belong to a team, sub-team etc. It
|
||||
// can also be used to represent the current state of a Match in scenarios such
|
||||
// as backfill, join-in-progress etc.
|
||||
message Roster {
|
||||
// A developer-chosen human-readable name for this Roster.
|
||||
string name = 1;
|
||||
|
||||
// If specified, only Tickets created before the specified time are selected.
|
||||
google.protobuf.Timestamp created_before = 6;
|
||||
|
||||
// If specified, only Tickets created after the specified time are selected.
|
||||
google.protobuf.Timestamp created_after = 7;
|
||||
|
||||
// Deprecated fields.
|
||||
reserved 3;
|
||||
// Tickets belonging to this Roster.
|
||||
repeated string ticket_ids = 2;
|
||||
}
|
||||
|
||||
// A MatchProfile is Open Match's representation of a Match specification. It is
|
||||
@ -190,22 +151,27 @@ message MatchProfile {
|
||||
// Name of this match profile.
|
||||
string name = 1;
|
||||
|
||||
// Set of properties associated with this MatchProfile. (Optional)
|
||||
// Open Match does not interpret these properties but passes them through to
|
||||
// the MatchFunction.
|
||||
google.protobuf.Struct properties = 2;
|
||||
|
||||
// Set of pools to be queried when generating a match for this MatchProfile.
|
||||
// The pool names can be used in empty Rosters to specify composition of a
|
||||
// match.
|
||||
repeated Pool pools = 3;
|
||||
|
||||
// Customized information not inspected by Open Match, to be used by the match
|
||||
// making function, evaluator, and components making calls to Open Match.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> extensions = 5;
|
||||
|
||||
// Deprecated fields.
|
||||
reserved 2, 4;
|
||||
// Set of Rosters for this match request. Could be empty Rosters used to
|
||||
// indicate the composition of the generated Match or they could be partially
|
||||
// pre-populated Ticket list to be used in scenarios such as backfill / join
|
||||
// in progress.
|
||||
repeated Roster rosters = 4;
|
||||
}
|
||||
|
||||
// A Match is used to represent a completed match object. It can be generated by
|
||||
// a MatchFunction as a proposal or can be returned by OpenMatch as a result in
|
||||
// response to the FetchMatches call.
|
||||
// When a match is returned by the FetchMatches call, it should contain at least
|
||||
// When a match is returned by the FetchMatches call, it should contain at least
|
||||
// one ticket to be considered as valid.
|
||||
message Match {
|
||||
// A Match ID that should be passed through the stack for tracing.
|
||||
@ -220,55 +186,9 @@ message Match {
|
||||
// Tickets belonging to this match.
|
||||
repeated Ticket tickets = 4;
|
||||
|
||||
// Customized information not inspected by Open Match, to be used by the match
|
||||
// making function, evaluator, and components making calls to Open Match.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> extensions = 7;
|
||||
// Set of Rosters that comprise this Match
|
||||
repeated Roster rosters = 5;
|
||||
|
||||
// Backfill request which contains additional information to the match
|
||||
// and contains an association to a GameServer.
|
||||
// BETA FEATURE WARNING: This field is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
Backfill backfill = 8;
|
||||
|
||||
// AllocateGameServer signalise Director that Backfill is new and it should
|
||||
// allocate a GameServer, this Backfill would be assigned.
|
||||
// BETA FEATURE WARNING: This field is not finalized and still subject
|
||||
// to possible change or removal.
|
||||
bool allocate_gameserver = 9;
|
||||
|
||||
// Deprecated fields.
|
||||
reserved 5, 6;
|
||||
// Match properties for this Match. Open Match does not interpret this field.
|
||||
google.protobuf.Struct properties = 6;
|
||||
}
|
||||
|
||||
// Represents a backfill entity which is used to fill partially full matches.
|
||||
//
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
message Backfill {
|
||||
// Id represents an auto-generated Id issued by Open Match.
|
||||
string id = 1;
|
||||
|
||||
// Search fields are the fields which Open Match is aware of, and can be used
|
||||
// when specifying filters.
|
||||
SearchFields search_fields = 2;
|
||||
|
||||
// Customized information not inspected by Open Match, to be used by
|
||||
// the Match Function, evaluator, and components making calls to Open Match.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> extensions = 3;
|
||||
|
||||
// Customized information not inspected by Open Match, to be kept persistent
|
||||
// throughout the life-cycle of a backfill.
|
||||
// Optional, depending on the requirements of the connected systems.
|
||||
map<string, google.protobuf.Any> persistent_field = 4;
|
||||
|
||||
// Create time is the time the Ticket was created. It is populated by Open
|
||||
// Match at the time of Ticket creation.
|
||||
google.protobuf.Timestamp create_time = 5;
|
||||
|
||||
// Generation gets incremented on GameServers update operations.
|
||||
// Prevents the MMF from overriding a newer version from the game server.
|
||||
// Do NOT read or write to this field, it is for internal tracking, and changing the value will cause bugs.
|
||||
int64 generation = 6;
|
||||
}
|
79
api/mmlogic.proto
Normal file
79
api/mmlogic.proto
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2019 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.
|
||||
|
||||
syntax = "proto3";
|
||||
package openmatch;
|
||||
option go_package = "open-match.dev/open-match/pkg/pb";
|
||||
option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "api/messages.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "protoc-gen-swagger/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "MM Logic (Data Layer)"
|
||||
version: "1.0"
|
||||
contact: {
|
||||
name: "Open Match"
|
||||
url: "https://open-match.dev"
|
||||
email: "open-match-discuss@googlegroups.com"
|
||||
}
|
||||
license: {
|
||||
name: "Apache 2.0 License"
|
||||
url: "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
}
|
||||
external_docs: {
|
||||
url: "https://open-match.dev/site/docs/"
|
||||
description: "Open Match Documentation"
|
||||
}
|
||||
schemes: HTTP
|
||||
schemes: HTTPS
|
||||
consumes: "application/json"
|
||||
produces: "application/json"
|
||||
responses: {
|
||||
key: "404"
|
||||
value: {
|
||||
description: "Returned when the resource does not exist."
|
||||
schema: { json_schema: { type: STRING } }
|
||||
}
|
||||
}
|
||||
// TODO Add annotations for security_defintiions.
|
||||
// See
|
||||
// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/proto/examplepb/a_bit_of_everything.proto
|
||||
};
|
||||
|
||||
message QueryTicketsRequest {
|
||||
// The Pool representing the set of Filters to be queried.
|
||||
Pool pool = 1;
|
||||
}
|
||||
|
||||
message QueryTicketsResponse {
|
||||
// The Tickets that meet the Filter criteria requested by the Pool.
|
||||
repeated Ticket tickets = 1;
|
||||
}
|
||||
|
||||
// The MMLogic API provides utility functions for common MMF functionality such
|
||||
// as retreiving Tickets from state storage.
|
||||
service MmLogic {
|
||||
// QueryTickets gets the list of Tickets that match every Filter in the
|
||||
// specified Pool.
|
||||
rpc QueryTickets(QueryTicketsRequest) returns (stream QueryTicketsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/mmlogic/tickets:query"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
282
api/mmlogic.swagger.json
Normal file
282
api/mmlogic.swagger.json
Normal file
@ -0,0 +1,282 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "MM Logic (Data Layer)",
|
||||
"version": "1.0",
|
||||
"contact": {
|
||||
"name": "Open Match",
|
||||
"url": "https://open-match.dev",
|
||||
"email": "open-match-discuss@googlegroups.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0 License",
|
||||
"url": "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
},
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/mmlogic/tickets:query": {
|
||||
"post": {
|
||||
"summary": "QueryTickets gets the list of Tickets that match every Filter in the\nspecified Pool.",
|
||||
"operationId": "QueryTickets",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"$ref": "#/x-stream-definitions/openmatchQueryTicketsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"format": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"MmLogic"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"openmatchAssignment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"connection": {
|
||||
"type": "string",
|
||||
"description": "Connection information for this Assignment."
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Other details to be sent to the players."
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus",
|
||||
"description": "Error when finding an Assignment for this Ticket."
|
||||
}
|
||||
},
|
||||
"description": "An Assignment object represents the assignment associated with a Ticket. Open\nmatch does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchBoolEqualsFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "boolean",
|
||||
"format": "boolean"
|
||||
}
|
||||
},
|
||||
"title": "Filters boolean values.\n attribute: \"foo\"\n value: false\nmatches:\n {\"foo\": false}\ndoes not match:\n {\"foo\": true}\n {\"foo\": \"bar\"}\n {\"foo\": 1}\n {\"foo\": \"false\"}\n {\"foo\": [false]}\n {\"foo\": null}\n {}"
|
||||
},
|
||||
"openmatchFloatRangeFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket attribute this Filter operates on."
|
||||
},
|
||||
"max": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Maximum value. Defaults to positive infinity (any value above minv)."
|
||||
},
|
||||
"min": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Minimum value. Defaults to 0."
|
||||
}
|
||||
},
|
||||
"title": "Filters numerical values to only those within a range.\n attribute: \"foo\"\n max: 10\n min: 5\nmatches:\n {\"foo\": 5}\n {\"foo\": 7.5}\n {\"foo\": 10}\ndoes not match:\n {\"foo\": 4}\n {\"foo\": 10.01}\n {\"foo\": \"7.5\"}\n {\"foo\": true}\n {\"foo\": [7.5]}\n {\"foo\": null}\n {}"
|
||||
},
|
||||
"openmatchPool": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "A developer-chosen human-readable name for this Pool."
|
||||
},
|
||||
"float_range_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchFloatRangeFilter"
|
||||
},
|
||||
"description": "Set of Filters indicating the filtering criteria. Selected players must\nmatch every Filter."
|
||||
},
|
||||
"bool_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchBoolEqualsFilter"
|
||||
}
|
||||
},
|
||||
"string_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchStringEqualsFilter"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchQueryTicketsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pool": {
|
||||
"$ref": "#/definitions/openmatchPool",
|
||||
"description": "The Pool representing the set of Filters to be queried."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchQueryTicketsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tickets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTicket"
|
||||
},
|
||||
"description": "The Tickets that meet the Filter criteria requested by the Pool."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchStringEqualsFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters strings exactly equaling a value.\n attribute: \"foo\"\n value: \"bar\"\nmatches:\n {\"foo\": \"bar\"}\ndoes not match:\n {\"foo\": \"baz\"}\n {\"foo\": true}\n {\"foo\": 5}\n {\"foo\": [\"bar\"]}\n {\"foo\": null}\n {}"
|
||||
},
|
||||
"openmatchTicket": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Ticket ID generated by Open Match."
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Properties contains custom info about the ticket. Top level values can be\nused in indexing and filtering to find tickets."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "Assignment associated with the Ticket."
|
||||
}
|
||||
},
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. In order to enter\nmatchmaking using Open Match, the client should generate a Ticket, passing in\nthe properties to be associated with this Ticket. Open Match will generate an\nID for a Ticket during creation. A Ticket could be used to represent an\nindividual 'Player' or a 'Group' of players. Open Match will not interpret\nwhat the Ticket represents but just treat it as a matchmaking unit with a set\nof properties. Open Match stores the Ticket in state storage and enables an\nAssignment to be associated with this Ticket."
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string",
|
||||
"description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "Must be a valid serialized protocol buffer of the above specified type."
|
||||
}
|
||||
},
|
||||
"description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := ptypes.MarshalAny(foo)\n ...\n foo := \u0026pb.Foo{}\n if err := ptypes.UnmarshalAny(any, foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"
|
||||
},
|
||||
"protobufNullValue": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NULL_VALUE"
|
||||
],
|
||||
"default": "NULL_VALUE",
|
||||
"description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\n The JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value."
|
||||
},
|
||||
"rpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The status code, which should be an enum value of\n[google.rpc.Code][google.rpc.Code]."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized\nby the client."
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use."
|
||||
}
|
||||
},
|
||||
"description": "- Simple to use and understand for most users\n- Flexible enough to meet unexpected needs\n\n# Overview\n\nThe `Status` message contains three pieces of data: error code, error\nmessage, and error details. The error code should be an enum value of\n[google.rpc.Code][google.rpc.Code], but it may accept additional error codes\nif needed. The error message should be a developer-facing English message\nthat helps developers *understand* and *resolve* the error. If a localized\nuser-facing error message is needed, put the localized message in the error\ndetails or localize it in the client. The optional error details may contain\narbitrary information about the error. There is a predefined set of error\ndetail types in the package `google.rpc` that can be used for common error\nconditions.\n\n# Language mapping\n\nThe `Status` message is the logical representation of the error model, but it\nis not necessarily the actual wire format. When the `Status` message is\nexposed in different client libraries and different wire protocols, it can be\nmapped differently. For example, it will likely be mapped to some exceptions\nin Java, but more likely mapped to some error codes in C.\n\n# Other uses\n\nThe error model and the `Status` message can be used in a variety of\nenvironments, either with or without APIs, to provide a\nconsistent developer experience across different environments.\n\nExample uses of this error model include:\n\n- Partial errors. If a service needs to return partial errors to the client,\n it may embed the `Status` in the normal response to indicate the partial\n errors.\n\n- Workflow errors. A typical workflow has multiple steps. Each step may\n have a `Status` message for error reporting.\n\n- Batch operations. If a client uses batch request and batch response, the\n `Status` message should be used directly inside batch response, one for\n each error sub-response.\n\n- Asynchronous operations. If an API call embeds asynchronous operation\n results in its response, the status of those operations should be\n represented directly using the `Status` message.\n\n- Logging. If some API errors are stored in logs, the message `Status` could\n be used directly after any stripping needed for security/privacy reasons.",
|
||||
"title": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). The error model is designed to be:"
|
||||
},
|
||||
"runtimeStreamError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"grpc_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"http_code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-stream-definitions": {
|
||||
"openmatchQueryTicketsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/runtimeStreamError"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchQueryTicketsResponse"
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Open Match Documentation",
|
||||
"url": "https://open-match.dev/site/docs/"
|
||||
}
|
||||
}
|
125
api/query.proto
125
api/query.proto
@ -1,125 +0,0 @@
|
||||
// Copyright 2019 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.
|
||||
|
||||
syntax = "proto3";
|
||||
package openmatch;
|
||||
option go_package = "open-match.dev/open-match/pkg/pb";
|
||||
option csharp_namespace = "OpenMatch";
|
||||
|
||||
import "api/messages.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "MM Logic (Data Layer)"
|
||||
version: "1.0"
|
||||
contact: {
|
||||
name: "Open Match"
|
||||
url: "https://open-match.dev"
|
||||
email: "open-match-discuss@googlegroups.com"
|
||||
}
|
||||
license: {
|
||||
name: "Apache 2.0 License"
|
||||
url: "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
}
|
||||
external_docs: {
|
||||
url: "https://open-match.dev/site/docs/"
|
||||
description: "Open Match Documentation"
|
||||
}
|
||||
schemes: HTTP
|
||||
schemes: HTTPS
|
||||
consumes: "application/json"
|
||||
produces: "application/json"
|
||||
responses: {
|
||||
key: "404"
|
||||
value: {
|
||||
description: "Returned when the resource does not exist."
|
||||
schema: { json_schema: { type: STRING } }
|
||||
}
|
||||
}
|
||||
// TODO Add annotations for security_defintiions.
|
||||
// See
|
||||
// https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/a_bit_of_everything.proto
|
||||
};
|
||||
|
||||
message QueryTicketsRequest {
|
||||
// The Pool representing the set of Filters to be queried.
|
||||
Pool pool = 1;
|
||||
}
|
||||
|
||||
message QueryTicketsResponse {
|
||||
// Tickets that meet all the filtering criteria requested by the pool.
|
||||
repeated Ticket tickets = 1;
|
||||
}
|
||||
|
||||
message QueryTicketIdsRequest {
|
||||
// The Pool representing the set of Filters to be queried.
|
||||
Pool pool = 1;
|
||||
}
|
||||
|
||||
message QueryTicketIdsResponse {
|
||||
// TicketIDs that meet all the filtering criteria requested by the pool.
|
||||
repeated string ids = 1;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request messages are not finalized and
|
||||
// still subject to possible change or removal.
|
||||
message QueryBackfillsRequest {
|
||||
// The Pool representing the set of Filters to be queried.
|
||||
Pool pool = 1;
|
||||
}
|
||||
|
||||
// BETA FEATURE WARNING: This Request messages are not finalized and
|
||||
// still subject to possible change or removal.
|
||||
message QueryBackfillsResponse {
|
||||
// Backfills that meet all the filtering criteria requested by the pool.
|
||||
repeated Backfill backfills = 1;
|
||||
}
|
||||
|
||||
// The QueryService service implements helper APIs for Match Function to query Tickets from state storage.
|
||||
service QueryService {
|
||||
// QueryTickets gets a list of Tickets that match all Filters of the input Pool.
|
||||
// - If the Pool contains no Filters, QueryTickets will return all Tickets in the state storage.
|
||||
// QueryTickets pages the Tickets by `queryPageSize` and stream back responses.
|
||||
// - queryPageSize is default to 1000 if not set, and has a minimum of 10 and maximum of 10000.
|
||||
rpc QueryTickets(QueryTicketsRequest) returns (stream QueryTicketsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/queryservice/tickets:query"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// QueryTicketIds gets the list of TicketIDs that meet all the filtering criteria requested by the pool.
|
||||
// - If the Pool contains no Filters, QueryTicketIds will return all TicketIDs in the state storage.
|
||||
// QueryTicketIds pages the TicketIDs by `queryPageSize` and stream back responses.
|
||||
// - queryPageSize is default to 1000 if not set, and has a minimum of 10 and maximum of 10000.
|
||||
rpc QueryTicketIds(QueryTicketIdsRequest) returns (stream QueryTicketIdsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/queryservice/ticketids:query"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// QueryBackfills gets a list of Backfills.
|
||||
// BETA FEATURE WARNING: This call and the associated Request and Response
|
||||
// messages are not finalized and still subject to possible change or removal.
|
||||
rpc QueryBackfills(QueryBackfillsRequest) returns (stream QueryBackfillsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/queryservice/backfills:query"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
@ -1,504 +0,0 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "MM Logic (Data Layer)",
|
||||
"version": "1.0",
|
||||
"contact": {
|
||||
"name": "Open Match",
|
||||
"url": "https://open-match.dev",
|
||||
"email": "open-match-discuss@googlegroups.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0 License",
|
||||
"url": "https://github.com/googleforgames/open-match/blob/master/LICENSE"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "QueryService"
|
||||
}
|
||||
],
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/v1/queryservice/backfills:query": {
|
||||
"post": {
|
||||
"summary": "QueryBackfills gets a list of Backfills.\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal.",
|
||||
"operationId": "QueryService_QueryBackfills",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchQueryBackfillsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchQueryBackfillsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchQueryBackfillsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"QueryService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/queryservice/ticketids:query": {
|
||||
"post": {
|
||||
"summary": "QueryTicketIds gets the list of TicketIDs that meet all the filtering criteria requested by the pool.\n - If the Pool contains no Filters, QueryTicketIds will return all TicketIDs in the state storage.\nQueryTicketIds pages the TicketIDs by `queryPageSize` and stream back responses.\n - queryPageSize is default to 1000 if not set, and has a minimum of 10 and maximum of 10000.",
|
||||
"operationId": "QueryService_QueryTicketIds",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketIdsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchQueryTicketIdsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketIdsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"QueryService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/queryservice/tickets:query": {
|
||||
"post": {
|
||||
"summary": "QueryTickets gets a list of Tickets that match all Filters of the input Pool.\n - If the Pool contains no Filters, QueryTickets will return all Tickets in the state storage.\nQueryTickets pages the Tickets by `queryPageSize` and stream back responses.\n - queryPageSize is default to 1000 if not set, and has a minimum of 10 and maximum of 10000.",
|
||||
"operationId": "QueryService_QueryTickets",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.(streaming responses)",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketsResponse"
|
||||
},
|
||||
"error": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
},
|
||||
"title": "Stream result of openmatchQueryTicketsResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Returned when the resource does not exist.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/openmatchQueryTicketsRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"QueryService"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"DoubleRangeFilterExclude": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NONE",
|
||||
"MIN",
|
||||
"MAX",
|
||||
"BOTH"
|
||||
],
|
||||
"default": "NONE",
|
||||
"title": "- NONE: No bounds should be excluded when evaluating the filter, i.e.: MIN \u003c= x \u003c= MAX\n - MIN: Only the minimum bound should be excluded when evaluating the filter, i.e.: MIN \u003c x \u003c= MAX\n - MAX: Only the maximum bound should be excluded when evaluating the filter, i.e.: MIN \u003c= x \u003c MAX\n - BOTH: Both bounds should be excluded when evaluating the filter, i.e.: MIN \u003c x \u003c MAX"
|
||||
},
|
||||
"openmatchAssignment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"connection": {
|
||||
"type": "string",
|
||||
"description": "Connection information for this Assignment."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
}
|
||||
},
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket.\nOpen Match does not require or inspect any fields on assignment."
|
||||
},
|
||||
"openmatchBackfill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by\nthe Match Function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a backfill. \nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
},
|
||||
"generation": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Generation gets incremented on GameServers update operations.\nPrevents the MMF from overriding a newer version from the game server.\nDo NOT read or write to this field, it is for internal tracking, and changing the value will cause bugs."
|
||||
}
|
||||
},
|
||||
"description": "Represents a backfill entity which is used to fill partially full matches.\n\nBETA FEATURE WARNING: This call and the associated Request and Response\nmessages are not finalized and still subject to possible change or removal."
|
||||
},
|
||||
"openmatchDoubleRangeFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_arg": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket's search_fields.double_args this Filter operates on."
|
||||
},
|
||||
"max": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Maximum value."
|
||||
},
|
||||
"min": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Minimum value."
|
||||
},
|
||||
"exclude": {
|
||||
"$ref": "#/definitions/DoubleRangeFilterExclude",
|
||||
"description": "Defines the bounds to apply when filtering tickets by their search_fields.double_args value.\nBETA FEATURE WARNING: This field and the associated values are\nnot finalized and still subject to possible change or removal."
|
||||
}
|
||||
},
|
||||
"title": "Filters numerical values to only those within a range.\n double_arg: \"foo\"\n max: 10\n min: 5\nmatches:\n {\"foo\": 5}\n {\"foo\": 7.5}\n {\"foo\": 10}\ndoes not match:\n {\"foo\": 4}\n {\"foo\": 10.01}\n {\"foo\": \"7.5\"}\n {}"
|
||||
},
|
||||
"openmatchPool": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "A developer-chosen human-readable name for this Pool."
|
||||
},
|
||||
"double_range_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchDoubleRangeFilter"
|
||||
},
|
||||
"description": "Set of Filters indicating the filtering criteria. Selected tickets must\nmatch every Filter."
|
||||
},
|
||||
"string_equals_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchStringEqualsFilter"
|
||||
}
|
||||
},
|
||||
"tag_present_filters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTagPresentFilter"
|
||||
}
|
||||
},
|
||||
"created_before": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "If specified, only Tickets created before the specified time are selected."
|
||||
},
|
||||
"created_after": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "If specified, only Tickets created after the specified time are selected."
|
||||
}
|
||||
},
|
||||
"description": "Pool specfies a set of criteria that are used to select a subset of Tickets\nthat meet all the criteria."
|
||||
},
|
||||
"openmatchQueryBackfillsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pool": {
|
||||
"$ref": "#/definitions/openmatchPool",
|
||||
"description": "The Pool representing the set of Filters to be queried."
|
||||
}
|
||||
},
|
||||
"description": "BETA FEATURE WARNING: This Request messages are not finalized and \nstill subject to possible change or removal."
|
||||
},
|
||||
"openmatchQueryBackfillsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backfills": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchBackfill"
|
||||
},
|
||||
"description": "Backfills that meet all the filtering criteria requested by the pool."
|
||||
}
|
||||
},
|
||||
"description": "BETA FEATURE WARNING: This Request messages are not finalized and \nstill subject to possible change or removal."
|
||||
},
|
||||
"openmatchQueryTicketIdsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pool": {
|
||||
"$ref": "#/definitions/openmatchPool",
|
||||
"description": "The Pool representing the set of Filters to be queried."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchQueryTicketIdsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "TicketIDs that meet all the filtering criteria requested by the pool."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchQueryTicketsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pool": {
|
||||
"$ref": "#/definitions/openmatchPool",
|
||||
"description": "The Pool representing the set of Filters to be queried."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchQueryTicketsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tickets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/openmatchTicket"
|
||||
},
|
||||
"description": "Tickets that meet all the filtering criteria requested by the pool."
|
||||
}
|
||||
}
|
||||
},
|
||||
"openmatchSearchFields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"double_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"description": "Float arguments. Filterable on ranges."
|
||||
},
|
||||
"string_args": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "String arguments. Filterable on equality."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filterable on presence or absence of given value."
|
||||
}
|
||||
},
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"openmatchStringEqualsFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_arg": {
|
||||
"type": "string",
|
||||
"description": "Name of the ticket's search_fields.string_args this Filter operates on."
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters strings exactly equaling a value.\n string_arg: \"foo\"\n value: \"bar\"\nmatches:\n {\"foo\": \"bar\"}\ndoes not match:\n {\"foo\": \"baz\"}\n {\"bar\": \"foo\"}\n {}"
|
||||
},
|
||||
"openmatchTagPresentFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "Filters to the tag being present on the search_fields.\n tag: \"foo\"\nmatches:\n [\"foo\"]\n [\"bar\",\"foo\"]\ndoes not match:\n [\"bar\"]\n []"
|
||||
},
|
||||
"openmatchTicket": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id represents an auto-generated Id issued by Open Match."
|
||||
},
|
||||
"assignment": {
|
||||
"$ref": "#/definitions/openmatchAssignment",
|
||||
"description": "An Assignment represents a game server assignment associated with a Ticket,\nor whatever finalized matched state means for your use case.\nOpen Match does not require or inspect any fields on Assignment."
|
||||
},
|
||||
"search_fields": {
|
||||
"$ref": "#/definitions/openmatchSearchFields",
|
||||
"description": "Search fields are the fields which Open Match is aware of, and can be used\nwhen specifying filters."
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be used by the match\nmaking function, evaluator, and components making calls to Open Match.\nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"persistent_field": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "Customized information not inspected by Open Match, to be kept persistent \nthroughout the life-cycle of a ticket. \nOptional, depending on the requirements of the connected systems."
|
||||
},
|
||||
"create_time": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Create time is the time the Ticket was created. It is populated by Open\nMatch at the time of Ticket creation."
|
||||
}
|
||||
},
|
||||
"description": "A Ticket is a basic matchmaking entity in Open Match. A Ticket may represent\nan individual 'Player', a 'Group' of players, or any other concepts unique to\nyour use case. Open Match will not interpret what the Ticket represents but\njust treat it as a matchmaking unit with a set of SearchFields. Open Match\nstores the Ticket in state storage and enables an Assignment to be set on the\nTicket."
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type_url": {
|
||||
"type": "string",
|
||||
"description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "Must be a valid serialized protocol buffer of the above specified type."
|
||||
}
|
||||
},
|
||||
"description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := ptypes.MarshalAny(foo)\n ...\n foo := \u0026pb.Foo{}\n if err := ptypes.UnmarshalAny(any, foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"
|
||||
},
|
||||
"rpcStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "A developer-facing error message, which should be in English. Any\nuser-facing error message should be localized and sent in the\n[google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client."
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufAny"
|
||||
},
|
||||
"description": "A list of messages that carry the error details. There is a common set of\nmessage types for APIs to use."
|
||||
}
|
||||
},
|
||||
"description": "The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors)."
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Open Match Documentation",
|
||||
"url": "https://open-match.dev/site/docs/"
|
||||
}
|
||||
}
|
@ -48,8 +48,8 @@
|
||||
|
||||
steps:
|
||||
- id: 'Docker Image: open-match-build'
|
||||
name: gcr.io/cloud-builders/docker
|
||||
args: ['build', '-t', 'gcr.io/$PROJECT_ID/open-match-build', '-f', 'Dockerfile.ci', '.']
|
||||
name: gcr.io/kaniko-project/executor
|
||||
args: ['--destination=gcr.io/$PROJECT_ID/open-match-build', '--cache=true', '--cache-ttl=48h', '--dockerfile=Dockerfile.ci', '.']
|
||||
waitFor: ['-']
|
||||
|
||||
- id: 'Build: Clean'
|
||||
@ -57,10 +57,10 @@ steps:
|
||||
args: ['make', 'clean-third-party', 'clean-protos', 'clean-swagger-docs']
|
||||
waitFor: ['Docker Image: open-match-build']
|
||||
|
||||
# - id: 'Test: Markdown'
|
||||
# name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
# args: ['make', 'md-test']
|
||||
# waitFor: ['Build: Clean']
|
||||
- id: 'Test: Markdown'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'md-test']
|
||||
waitFor: ['Build: Clean']
|
||||
|
||||
- id: 'Setup: Download Dependencies'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
@ -72,7 +72,7 @@ steps:
|
||||
|
||||
- id: 'Build: Initialize Toolchain'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'install-toolchain']
|
||||
args: ['make', 'install-toolchain', 'push-helm-ci']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
@ -90,7 +90,7 @@ steps:
|
||||
|
||||
- id: 'Build: Assets'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', '_CHARTS_BUCKET=${_CHARTS_BUCKET}', 'assets', '-j12']
|
||||
args: ['make', 'assets', '-j12']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
path: '/go'
|
||||
@ -127,12 +127,12 @@ steps:
|
||||
|
||||
- id: 'Test: Deploy Open Match'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'SHORT_SHA=${SHORT_SHA}', 'OPEN_MATCH_KUBERNETES_NAMESPACE=open-match-${BUILD_ID}', 'OPEN_MATCH_RELEASE_NAME=open-match-${BUILD_ID}', 'auth-gke-cluster', 'delete-chart', 'ci-reap-namespaces', 'install-ci-chart']
|
||||
args: ['make', 'SHORT_SHA=${SHORT_SHA}', 'OPEN_MATCH_KUBERNETES_NAMESPACE=open-match-${SHORT_SHA}', 'OPEN_MATCH_RELEASE_NAME=open-match-${SHORT_SHA}', 'auth-gke-cluster', 'delete-chart', 'ci-reap-namespaces', 'install-ci-chart']
|
||||
waitFor: ['Build: Docker Images']
|
||||
|
||||
- id: 'Deploy: Deployment Configs'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', '_GCB_POST_SUBMIT=${_GCB_POST_SUBMIT}', '_GCB_LATEST_VERSION=${_GCB_LATEST_VERSION}', 'SHORT_SHA=${SHORT_SHA}', 'BRANCH_NAME=${BRANCH_NAME}', '_CHARTS_BUCKET=${_CHARTS_BUCKET}', 'ci-deploy-artifacts']
|
||||
args: ['make', '_GCB_POST_SUBMIT=${_GCB_POST_SUBMIT}', '_GCB_LATEST_VERSION=${_GCB_LATEST_VERSION}', 'SHORT_SHA=${SHORT_SHA}', 'BRANCH_NAME=${BRANCH_NAME}', 'ci-deploy-artifacts']
|
||||
waitFor: ['Lint: Format, Vet, Charts', 'Test: Deploy Open Match']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
@ -140,7 +140,7 @@ steps:
|
||||
|
||||
- id: 'Test: End-to-End Cluster'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'GOPROXY=off', 'SHORT_SHA=${SHORT_SHA}', 'OPEN_MATCH_KUBERNETES_NAMESPACE=open-match-${BUILD_ID}', 'test-e2e-cluster']
|
||||
args: ['make', 'GOPROXY=off', 'SHORT_SHA=${SHORT_SHA}', 'OPEN_MATCH_KUBERNETES_NAMESPACE=open-match-${SHORT_SHA}', 'test-e2e-cluster']
|
||||
waitFor: ['Test: Deploy Open Match', 'Build: Assets']
|
||||
volumes:
|
||||
- name: 'go-vol'
|
||||
@ -148,12 +148,12 @@ steps:
|
||||
|
||||
- id: 'Test: Delete Open Match'
|
||||
name: 'gcr.io/$PROJECT_ID/open-match-build'
|
||||
args: ['make', 'GCLOUD_EXTRA_FLAGS=--async', 'SHORT_SHA=${SHORT_SHA}', 'OPEN_MATCH_KUBERNETES_NAMESPACE=open-match-${BUILD_ID}', 'OPEN_MATCH_RELEASE_NAME=open-match-${BUILD_ID}', 'GCP_PROJECT_ID=${PROJECT_ID}', 'delete-chart']
|
||||
args: ['make', 'GCLOUD_EXTRA_FLAGS=--async', 'SHORT_SHA=${SHORT_SHA}', 'OPEN_MATCH_KUBERNETES_NAMESPACE=open-match-${SHORT_SHA}', 'GCP_PROJECT_ID=${PROJECT_ID}', 'delete-chart']
|
||||
waitFor: ['Test: End-to-End Cluster']
|
||||
|
||||
artifacts:
|
||||
objects:
|
||||
location: '${_ARTIFACTS_BUCKET}'
|
||||
location: gs://open-match-build-artifacts/output/
|
||||
paths:
|
||||
- install/yaml/install.yaml
|
||||
- install/yaml/01-open-match-core.yaml
|
||||
@ -161,16 +161,12 @@ artifacts:
|
||||
- install/yaml/03-prometheus-chart.yaml
|
||||
- install/yaml/04-grafana-chart.yaml
|
||||
- install/yaml/05-jaeger-chart.yaml
|
||||
- install/yaml/06-open-match-override-configmap.yaml
|
||||
|
||||
|
||||
substitutions:
|
||||
_OM_VERSION: "0.0.0-dev"
|
||||
_OM_VERSION: "0.7.0"
|
||||
_GCB_POST_SUBMIT: "0"
|
||||
_GCB_LATEST_VERSION: "undefined"
|
||||
_ARTIFACTS_BUCKET: "gs://open-match-build-artifacts/output/"
|
||||
_LOGS_BUCKET: "gs://open-match-build-logs/"
|
||||
_CHARTS_BUCKET: "gs://open-match-chart"
|
||||
logsBucket: '${_LOGS_BUCKET}'
|
||||
logsBucket: 'gs://open-match-build-logs/'
|
||||
options:
|
||||
sourceProvenanceHash: ['SHA256']
|
||||
machineType: 'N1_HIGHCPU_32'
|
||||
|
@ -16,10 +16,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/internal/app"
|
||||
"open-match.dev/open-match/internal/app/backend"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("backend", backend.BindService)
|
||||
app.RunApplication("backend", backend.BindService)
|
||||
}
|
||||
|
@ -16,10 +16,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/internal/app"
|
||||
"open-match.dev/open-match/internal/app/frontend"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("frontend", frontend.BindService)
|
||||
app.RunApplication("frontend", frontend.BindService)
|
||||
}
|
||||
|
@ -16,10 +16,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/internal/app"
|
||||
"open-match.dev/open-match/internal/app/minimatch"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("minimatch", minimatch.BindService)
|
||||
app.RunApplication("minimatch", minimatch.BindService)
|
||||
}
|
||||
|
@ -12,14 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package main is the query service for Open Match.
|
||||
// Package main is the mmlogic service for Open Match.
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/internal/app/query"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
"open-match.dev/open-match/internal/app"
|
||||
"open-match.dev/open-match/internal/app/mmlogic"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("query", query.BindService)
|
||||
app.RunApplication("mmlogic", mmlogic.BindService)
|
||||
}
|
@ -16,9 +16,8 @@ package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/examples/scale/backend"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("scale", backend.BindService)
|
||||
backend.Run()
|
||||
}
|
||||
|
@ -16,9 +16,8 @@ package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/examples/scale/frontend"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("scale", frontend.BindService)
|
||||
frontend.Run()
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
"urls": [
|
||||
{"name": "Frontend", "url": "https://open-match.dev/api/v0.0.0-dev/frontend.swagger.json"},
|
||||
{"name": "Backend", "url": "https://open-match.dev/api/v0.0.0-dev/backend.swagger.json"},
|
||||
{"name": "Query", "url": "https://open-match.dev/api/v0.0.0-dev/query.swagger.json"},
|
||||
{"name": "Mmlogic", "url": "https://open-match.dev/api/v0.0.0-dev/mmlogic.swagger.json"},
|
||||
{"name": "MatchFunction", "url": "https://open-match.dev/api/v0.0.0-dev/matchfunction.swagger.json"},
|
||||
{"name": "Synchronizer", "url": "https://open-match.dev/api/v0.0.0-dev/synchronizer.swagger.json"},
|
||||
{"name": "Evaluator", "url": "https://open-match.dev/api/v0.0.0-dev/evaluator.swagger.json"}
|
||||
|
@ -16,10 +16,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/internal/app"
|
||||
"open-match.dev/open-match/internal/app/synchronizer"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appmain.RunApplication("synchronizer", synchronizer.BindService)
|
||||
app.RunApplication("synchronizer", synchronizer.BindService)
|
||||
}
|
||||
|
12
csharp/OpenMatch/OpenMatch.csproj
Normal file
12
csharp/OpenMatch/OpenMatch.csproj
Normal file
@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<PackageId>OpenMatch</PackageId>
|
||||
<Version>0.0.0-dev</Version>
|
||||
<Authors>Google LLC</Authors>
|
||||
<Company>Google LLC</Company>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
@ -9,12 +9,14 @@ To build Open Match you'll need the following applications installed.
|
||||
|
||||
* [Git](https://git-scm.com/downloads)
|
||||
* [Go](https://golang.org/doc/install)
|
||||
* [Python3 with virtualenv](https://wiki.python.org/moin/BeginnersGuide/Download)
|
||||
* Make (Mac: install [XCode](https://itunes.apple.com/us/app/xcode/id497799835))
|
||||
* [Docker](https://docs.docker.com/install/) including the
|
||||
[post-install steps](https://docs.docker.com/install/linux/linux-postinstall/).
|
||||
|
||||
Optional Software
|
||||
|
||||
* [Google Cloud Platform](gcloud.md)
|
||||
* [Visual Studio Code](https://code.visualstudio.com/Download) for IDE.
|
||||
Vim and Emacs work to.
|
||||
* [VirtualBox](https://www.virtualbox.org/wiki/Downloads) recommended for
|
||||
@ -25,7 +27,8 @@ running:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -q make google-cloud-sdk git unzip tar
|
||||
sudo apt-get install -y -q python3 python3-virtualenv virtualenv make \
|
||||
google-cloud-sdk git unzip tar
|
||||
```
|
||||
|
||||
*It's recommended that you install Go using their instructions because package
|
||||
@ -46,13 +49,15 @@ make
|
||||
|
||||
*Typically for contributing you'll want to
|
||||
[create a fork](https://help.github.com/en/articles/fork-a-repo) and use that
|
||||
but for purpose of this guide we'll be using the upstream/main.*
|
||||
but for purpose of this guide we'll be using the upstream/master.*
|
||||
|
||||
## Building code and images
|
||||
## Building
|
||||
|
||||
```bash
|
||||
# Reset workspace
|
||||
make clean
|
||||
# Compile all the binaries
|
||||
make all -j$(nproc)
|
||||
# Run tests
|
||||
make test
|
||||
# Build all the images.
|
||||
@ -61,8 +66,6 @@ make build-images -j$(nproc)
|
||||
make push-images -j$(nproc)
|
||||
# Push images to Docker Hub
|
||||
make REGISTRY=mydockerusername push-images -j$(nproc)
|
||||
# Generate Kubernetes installation YAML files (Note that the trailing '/' is needed here)
|
||||
make install/yaml/
|
||||
```
|
||||
|
||||
_**-j$(nproc)** is a flag to tell make to parallelize the commands based on
|
||||
@ -82,9 +85,11 @@ default context the Makefile will honor that._
|
||||
# GKE cluster: make create-gke-cluster/delete-gke-cluster
|
||||
# or create a local Minikube cluster
|
||||
make create-gke-cluster
|
||||
# Step 2: Build and Push Open Match Images to gcr.io
|
||||
# Step 2: Download helm and install Tiller in the cluster
|
||||
make push-helm
|
||||
# Step 3: Build and Push Open Match Images to gcr.io
|
||||
make push-images -j$(nproc)
|
||||
# Step 3: Install Open Match in the cluster.
|
||||
# Install Open Match in the cluster.
|
||||
make install-chart
|
||||
|
||||
# Create a proxy to Open Match pods so that you can access them locally.
|
||||
@ -98,36 +103,19 @@ make proxy
|
||||
make delete-chart
|
||||
```
|
||||
|
||||
## Iterating
|
||||
While iterating on the project, you may need to:
|
||||
1. Install/Run everything
|
||||
2. Make some code changes
|
||||
3. Make sure the changes compile by running `make test`
|
||||
4. Build and push Docker images to your personal registry by running `make push-images -j$(nproc)`
|
||||
5. Deploy the code change by running `make install-chart`
|
||||
6. Verify it's working by [looking at the logs](#accessing-logs) or looking at the monitoring dashboard by running `make proxy-grafana`
|
||||
7. Tear down Open Match by running `make delete-chart`
|
||||
## Interaction
|
||||
|
||||
## Accessing logs
|
||||
To look at Open Match core services' logs, run:
|
||||
```bash
|
||||
# Replace open-match-frontend with the service name that you would like to access
|
||||
kubectl logs -n open-match svc/open-match-frontend
|
||||
```
|
||||
Before integrating with Open Match you can manually interact with it to get a feel for how it works.
|
||||
|
||||
## API References
|
||||
While integrating with Open Match you may want to understand its API surface concepts or interact with it and get a feel for how it works.
|
||||
|
||||
The APIs are defined in `proto` format under the `api/` folder, with references available at [open-match.dev](https://open-match.dev/site/docs/reference/api/).
|
||||
|
||||
You can also run `make proxy-ui` to exposes the Swagger UI for Open Match locally on your computer after [deploying it to Kubernetes](#deploying-to-kubernetes), then go to http://localhost:51500 and view the REST APIs as well as interactively call Open Match.
|
||||
`make proxy-ui` exposes the Swagger UI for Open Match locally on your computer.
|
||||
You can then go to http://localhost:51500 and view the API as well as interactively call Open Match.
|
||||
|
||||
By default you will be talking to the frontend server but you can change the target API url to any of the following:
|
||||
|
||||
* api/frontend.swagger.json
|
||||
* api/backend.swagger.json
|
||||
* api/synchronizer.swagger.json
|
||||
* api/query.swagger.json
|
||||
* api/mmlogic.swagger.json
|
||||
|
||||
For a more current list refer to the api/ directory of this repository. Also matchfunction.swagger.json is not supported.
|
||||
|
||||
@ -154,9 +142,55 @@ export GOPATH=$HOME/workspace/
|
||||
|
||||
## Pull Requests
|
||||
|
||||
If you want to submit a Pull Request, `make presubmit` can catch most of the issues your change can run into.
|
||||
If you want to submit a Pull Request there's some tools to help prepare your
|
||||
change.
|
||||
|
||||
```bash
|
||||
# Runs code generators, tests, and linters.
|
||||
make presubmit
|
||||
```
|
||||
|
||||
`make presubmit` catches most of the issues your change can run into. If the
|
||||
submit checks fail you can run it locally via,
|
||||
|
||||
```bash
|
||||
make local-cloud-build
|
||||
```
|
||||
|
||||
Our [continuous integration](https://console.cloud.google.com/cloud-build/builds?project=open-match-build)
|
||||
runs against all PRs. In order to see your build results you'll need to
|
||||
become a member of
|
||||
[open-match-discuss@googlegroups.com](https://groups.google.com/forum/#!forum/open-match-discuss).
|
||||
|
||||
|
||||
## Makefile
|
||||
|
||||
The Makefile is the core of Open Match's build process. There's a lot of
|
||||
commands but here's a list of the important ones and patterns to remember them.
|
||||
|
||||
```bash
|
||||
# Help
|
||||
make
|
||||
|
||||
# Reset workspace (delete all build artifacts)
|
||||
make clean
|
||||
# Delete auto-generated protobuf code and swagger API docs.
|
||||
make clean-protos clean-swagger-docs
|
||||
# make clean-* deletes some part of the build outputs.
|
||||
|
||||
# Build all Docker images
|
||||
make build-images
|
||||
# Build frontend docker image.
|
||||
make build-frontend-image
|
||||
|
||||
# Formats, Vets, and tests the codebase.
|
||||
make fmt vet test
|
||||
# Same as above also regenerates autogen files.
|
||||
make presubmit
|
||||
|
||||
# Run website on http://localhost:8080
|
||||
make run-site
|
||||
|
||||
# Proxy all Open Match processes to view them.
|
||||
make proxy
|
||||
```
|
||||
|
26
docs/gcloud.md
Normal file
26
docs/gcloud.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Create a GKE Cluster
|
||||
|
||||
Below are the steps to create a GKE cluster in Google Cloud Platform.
|
||||
|
||||
* Create a GCP project via [Google Cloud Console](https://console.cloud.google.com/).
|
||||
* Billing must be enabled. If you're a new customer you can get some [free credits](https://cloud.google.com/free/).
|
||||
* When you create a project you'll need to set a Project ID, if you forget it you can see it here, https://console.cloud.google.com/iam-admin/settings/project.
|
||||
* Install [Google Cloud SDK](https://cloud.google.com/sdk/) which is the command line tool to work against your project.
|
||||
|
||||
Here are the next steps using the gcloud tool.
|
||||
|
||||
```bash
|
||||
# Login to your Google Account for GCP
|
||||
gcloud auth login
|
||||
gcloud config set project $YOUR_GCP_PROJECT_ID
|
||||
|
||||
# Enable necessary GCP services
|
||||
gcloud services enable containerregistry.googleapis.com
|
||||
gcloud services enable container.googleapis.com
|
||||
|
||||
# Test that everything is good, this command should work.
|
||||
gcloud compute zones list
|
||||
|
||||
# Create a GKE Cluster in this project
|
||||
gcloud container clusters create --machine-type n1-standard-2 open-match-dev-cluster --zone us-west1-a --tags open-match
|
||||
```
|
@ -2,29 +2,23 @@
|
||||
|
||||
This is the {version} release of Open Match.
|
||||
|
||||
Check the [official website](https://open-match.dev) for details on features, installation and usage.
|
||||
Check the [README](https://github.com/googleforgames/open-match/tree/release-{version}) for details on features, installation and usage.
|
||||
|
||||
Release Notes
|
||||
-------------
|
||||
|
||||
**Feature Highlights**
|
||||
{ highlight here the most notable changes and themes at a high level}
|
||||
{ insert enhancements from the changelog and/or security and breaking changes }
|
||||
|
||||
**Breaking Changes**
|
||||
{ detail any behaviors or API surfaces which worked in a previous version which will no longer work correctly }
|
||||
|
||||
> Future releases towards 1.0.0 may still have breaking changes.
|
||||
|
||||
**Security Fixes**
|
||||
{ list any changes which fix vulnerabilities in open match }
|
||||
* API Changed #PR
|
||||
|
||||
**Enhancements**
|
||||
{ go into details on improvements and changes }
|
||||
* New Harness #PR
|
||||
|
||||
Usage Requirements
|
||||
-------------
|
||||
* Tested against Kubernetes Version { a list of k8s versions}
|
||||
* Golang Version = v{ required golang version }
|
||||
**Security Fixes**
|
||||
* Reduced privileges required for MMF. #PR
|
||||
|
||||
See [CHANGELOG](https://github.com/googleforgames/open-match/blob/release-{version}/CHANGELOG.md) for more details on changes.
|
||||
|
||||
Images
|
||||
------
|
||||
@ -33,7 +27,7 @@ Images
|
||||
# Servers
|
||||
docker pull gcr.io/open-match-public-images/openmatch-backend:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-frontend:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-query:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-mmlogic:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-synchronizer:{version}
|
||||
|
||||
# Evaluators
|
||||
@ -44,7 +38,7 @@ docker pull gcr.io/open-match-public-images/openmatch-mmf-go-soloduel:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-mmf-go-pool:{version}
|
||||
|
||||
# Test Clients
|
||||
docker pull gcr.io/open-match-public-images/openmatch-demo-first-match:{version}
|
||||
docker pull gcr.io/open-match-public-images/openmatch-demo:{version}
|
||||
```
|
||||
|
||||
_This software is currently alpha, and subject to change. Not to be used in production systems._
|
||||
@ -52,10 +46,15 @@ _This software is currently alpha, and subject to change. Not to be used in prod
|
||||
Installation
|
||||
------------
|
||||
|
||||
* Follow [Open Match Installation Guide](https://open-match.dev/site/docs/installation/) to setup Open Match in your cluster.
|
||||
To deploy Open Match in your Kubernetes cluster run the following commands:
|
||||
|
||||
API Definitions
|
||||
------------
|
||||
|
||||
- gRPC API Definitions are available in [API references](https://open-match.dev/site/docs/reference/api/) - _Preferred_
|
||||
- HTTP API Definitions are available in [SwaggerUI](https://open-match.dev/site/swaggerui/index.html)
|
||||
```bash
|
||||
# Grant yourself cluster-admin permissions so that you can deploy service accounts.
|
||||
kubectl create clusterrolebinding myname-cluster-admin-binding --clusterrole=cluster-admin --user=$(YOUR_KUBERNETES_USER_NAME)
|
||||
# Place all Open Match components in their own namespace.
|
||||
kubectl create namespace open-match
|
||||
# Install Open Match and monitoring services.
|
||||
kubectl apply -f https://github.com/googleforgames/open-match/releases/download/v{version}/install.yaml --namespace open-match
|
||||
# Install the demo.
|
||||
kubectl apply -f https://github.com/googleforgames/open-match/releases/download/v{version}/install-demo.yaml --namespace open-match
|
||||
```
|
||||
|
@ -12,13 +12,24 @@ SOURCE_VERSION=$1
|
||||
DEST_VERSION=$2
|
||||
SOURCE_PROJECT_ID=open-match-build
|
||||
DEST_PROJECT_ID=open-match-public-images
|
||||
IMAGE_NAMES=$(make list-images)
|
||||
IMAGE_NAMES="openmatch-backend openmatch-frontend openmatch-mmlogic openmatch-synchronizer openmatch-minimatch openmatch-demo openmatch-mmf-go-soloduel openmatch-mmf-go-pool openmatch-evaluator-go-simple openmatch-swaggerui openmatch-reaper"
|
||||
|
||||
for name in $IMAGE_NAMES
|
||||
do
|
||||
source_image=gcr.io/$SOURCE_PROJECT_ID/openmatch-$name:$SOURCE_VERSION
|
||||
dest_image=gcr.io/$DEST_PROJECT_ID/openmatch-$name:$DEST_VERSION
|
||||
source_image=gcr.io/$SOURCE_PROJECT_ID/$name:$SOURCE_VERSION
|
||||
dest_image=gcr.io/$DEST_PROJECT_ID/$name:$DEST_VERSION
|
||||
docker pull $source_image
|
||||
docker tag $source_image $dest_image
|
||||
docker push $dest_image
|
||||
done
|
||||
|
||||
echo "=============================================================="
|
||||
echo "=============================================================="
|
||||
echo "=============================================================="
|
||||
echo "=============================================================="
|
||||
|
||||
echo "Add these lines to your release notes:"
|
||||
for name in $IMAGE_NAMES
|
||||
do
|
||||
echo "docker pull gcr.io/$DEST_PROJECT_ID/$name:$DEST_VERSION"
|
||||
done
|
||||
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
title: "Open Match API References"
|
||||
linkTitle: "Open Match API References"
|
||||
weight: 2
|
||||
description:
|
||||
This document provides API references for Open Match services.
|
||||
---
|
@ -11,17 +11,10 @@
|
||||
// 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 (
|
||||
"open-match.dev/open-match/tutorials/matchmaker101/evaluator/evaluate"
|
||||
)
|
||||
// Package examples defines the constants that some of the examples may share.
|
||||
package examples
|
||||
|
||||
const (
|
||||
// Replace this with the port on which your Evaluator service is exposed.
|
||||
evaluatorPort = 50508
|
||||
MatchScore = "match_score"
|
||||
)
|
||||
|
||||
func main() {
|
||||
evaluate.Start(evaluatorPort)
|
||||
}
|
@ -37,7 +37,7 @@ func New() *ByteSub {
|
||||
}
|
||||
}
|
||||
|
||||
// AnnounceLatest writes b to all of the subscribers, with caveats listed in Subscribe.
|
||||
// AnnounceLatest writes b to all of the subscribers, with caviets listed in Subscribe.
|
||||
func (s *ByteSub) AnnounceLatest(b []byte) {
|
||||
s.r.Lock()
|
||||
defer s.r.Unlock()
|
||||
|
@ -51,7 +51,7 @@ func TestFastAndSlow(t *testing.T) {
|
||||
for count := 0; true; count++ {
|
||||
if v := <-slow; v == "3" {
|
||||
if count > 1 {
|
||||
t.Error("Expected to receive at most 1 other value on slow before receiving the latest value.")
|
||||
t.Error("Expected to recieve at most 1 other value on slow before recieving the latest value.")
|
||||
}
|
||||
break
|
||||
}
|
||||
|
@ -20,11 +20,12 @@ import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"open-match.dev/open-match/examples/demo/components"
|
||||
"open-match.dev/open-match/examples/demo/updater"
|
||||
"open-match.dev/open-match/internal/config"
|
||||
"open-match.dev/open-match/internal/rpc"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
"open-match.dev/open-match/pkg/structs"
|
||||
)
|
||||
|
||||
func Run(ds *components.DemoShared) {
|
||||
@ -34,7 +35,7 @@ func Run(ds *components.DemoShared) {
|
||||
name := fmt.Sprintf("fakeplayer_%d", i)
|
||||
go func() {
|
||||
for !isContextDone(ds.Ctx) {
|
||||
runScenario(ds.Ctx, name, u.ForField(name))
|
||||
runScenario(ds.Ctx, ds.Cfg, name, u.ForField(name))
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -54,7 +55,7 @@ type status struct {
|
||||
Assignment *pb.Assignment
|
||||
}
|
||||
|
||||
func runScenario(ctx context.Context, name string, update updater.SetFunc) {
|
||||
func runScenario(ctx context.Context, cfg config.View, name string, update updater.SetFunc) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
@ -80,13 +81,12 @@ func runScenario(ctx context.Context, name string, update updater.SetFunc) {
|
||||
s.Status = "Connecting to Open Match frontend"
|
||||
update(s)
|
||||
|
||||
// See https://open-match.dev/site/docs/guides/api/
|
||||
conn, err := grpc.Dial("open-match-frontend.open-match.svc.cluster.local:50504", grpc.WithInsecure())
|
||||
conn, err := rpc.GRPCClientFromConfig(cfg, "api.frontend")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
fe := pb.NewFrontendServiceClient(conn)
|
||||
fe := pb.NewFrontendClient(conn)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
s.Status = "Creating Open Match Ticket"
|
||||
@ -95,14 +95,19 @@ func runScenario(ctx context.Context, name string, update updater.SetFunc) {
|
||||
var ticketId string
|
||||
{
|
||||
req := &pb.CreateTicketRequest{
|
||||
Ticket: &pb.Ticket{},
|
||||
Ticket: &pb.Ticket{
|
||||
Properties: structs.Struct{
|
||||
"name": structs.String(name),
|
||||
"mode.demo": structs.Number(1),
|
||||
}.S(),
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := fe.CreateTicket(ctx, req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ticketId = resp.Id
|
||||
ticketId = resp.Ticket.Id
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@ -111,11 +116,11 @@ func runScenario(ctx context.Context, name string, update updater.SetFunc) {
|
||||
|
||||
var assignment *pb.Assignment
|
||||
{
|
||||
req := &pb.WatchAssignmentsRequest{
|
||||
req := &pb.GetAssignmentsRequest{
|
||||
TicketId: ticketId,
|
||||
}
|
||||
|
||||
stream, err := fe.WatchAssignments(ctx, req)
|
||||
stream, err := fe.GetAssignments(ctx, req)
|
||||
for assignment.GetConnection() == "" {
|
||||
resp, err := stream.Recv()
|
||||
if err != nil {
|
||||
|
@ -18,9 +18,11 @@ import (
|
||||
"context"
|
||||
|
||||
"open-match.dev/open-match/examples/demo/updater"
|
||||
"open-match.dev/open-match/internal/config"
|
||||
)
|
||||
|
||||
type DemoShared struct {
|
||||
Ctx context.Context
|
||||
Cfg config.View
|
||||
Update updater.SetFunc
|
||||
}
|
||||
|
@ -21,9 +21,8 @@ import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"open-match.dev/open-match/examples/demo/components"
|
||||
"open-match.dev/open-match/internal/rpc"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
@ -67,13 +66,12 @@ func run(ds *components.DemoShared) {
|
||||
s.Status = "Connecting to backend"
|
||||
ds.Update(s)
|
||||
|
||||
// See https://open-match.dev/site/docs/guides/api/
|
||||
conn, err := grpc.Dial("open-match-backend.open-match.svc.cluster.local:50505", grpc.WithInsecure())
|
||||
conn, err := rpc.GRPCClientFromConfig(ds.Cfg, "api.backend")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
be := pb.NewBackendServiceClient(conn)
|
||||
be := pb.NewBackendClient(conn)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
s.Status = "Match Match: Sending Request"
|
||||
@ -83,15 +81,24 @@ func run(ds *components.DemoShared) {
|
||||
{
|
||||
req := &pb.FetchMatchesRequest{
|
||||
Config: &pb.FunctionConfig{
|
||||
Host: "om-function.open-match-demo.svc.cluster.local",
|
||||
Port: 50502,
|
||||
Host: ds.Cfg.GetString("api.functions.hostname"),
|
||||
Port: int32(ds.Cfg.GetInt("api.functions.grpcport")),
|
||||
Type: pb.FunctionConfig_GRPC,
|
||||
},
|
||||
Profile: &pb.MatchProfile{
|
||||
Name: "1v1",
|
||||
Pools: []*pb.Pool{
|
||||
{
|
||||
Name: "Everyone",
|
||||
Profiles: []*pb.MatchProfile{
|
||||
{
|
||||
Name: "1v1",
|
||||
Pools: []*pb.Pool{
|
||||
{
|
||||
Name: "Everyone",
|
||||
FloatRangeFilters: []*pb.FloatRangeFilter{
|
||||
{
|
||||
Attribute: "mode.demo",
|
||||
Min: -100,
|
||||
Max: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -131,13 +138,9 @@ func run(ds *components.DemoShared) {
|
||||
}
|
||||
|
||||
req := &pb.AssignTicketsRequest{
|
||||
Assignments: []*pb.AssignmentGroup{
|
||||
{
|
||||
TicketIds: ids,
|
||||
Assignment: &pb.Assignment{
|
||||
Connection: fmt.Sprintf("%d.%d.%d.%d:2222", rand.Intn(256), rand.Intn(256), rand.Intn(256), rand.Intn(256)),
|
||||
},
|
||||
},
|
||||
TicketIds: ids,
|
||||
Assignment: &pb.Assignment{
|
||||
Connection: fmt.Sprintf("%d.%d.%d.%d:2222", rand.Intn(256), rand.Intn(256), rand.Intn(256), rand.Intn(256)),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -20,20 +20,37 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/websocket"
|
||||
"open-match.dev/open-match/examples/demo/bytesub"
|
||||
"open-match.dev/open-match/examples/demo/components"
|
||||
"open-match.dev/open-match/examples/demo/updater"
|
||||
"open-match.dev/open-match/internal/config"
|
||||
"open-match.dev/open-match/internal/logging"
|
||||
"open-match.dev/open-match/internal/telemetry"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = logrus.WithFields(logrus.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "examples.demo",
|
||||
})
|
||||
)
|
||||
|
||||
// Run starts the provided components, and hosts a webserver for observing the
|
||||
// output of those components.
|
||||
func Run(comps map[string]func(*components.DemoShared)) {
|
||||
log.Print("Initializing Server")
|
||||
cfg, err := config.Read()
|
||||
if err != nil {
|
||||
logger.WithFields(logrus.Fields{
|
||||
"error": err.Error(),
|
||||
}).Fatalf("cannot read configuration.")
|
||||
}
|
||||
logging.ConfigureLogging(cfg)
|
||||
|
||||
logger.Info("Initializing Server")
|
||||
|
||||
fileServe := http.FileServer(http.Dir("/app/static"))
|
||||
http.Handle("/static/", http.StripPrefix("/static/", fileServe))
|
||||
@ -62,16 +79,17 @@ func Run(comps map[string]func(*components.DemoShared)) {
|
||||
bs.Subscribe(ws.Request().Context(), ws)
|
||||
}))
|
||||
|
||||
log.Print("Starting Server")
|
||||
logger.Info("Starting Server")
|
||||
|
||||
for name, f := range comps {
|
||||
go f(&components.DemoShared{
|
||||
Ctx: context.Background(),
|
||||
Cfg: cfg,
|
||||
Update: u.ForField(name),
|
||||
})
|
||||
}
|
||||
|
||||
address := fmt.Sprintf(":%d", 51507)
|
||||
err := http.ListenAndServe(address, nil)
|
||||
log.Printf("HTTP server closed: %s", err.Error())
|
||||
address := fmt.Sprintf(":%d", cfg.GetInt("api.demo.httpport"))
|
||||
err = http.ListenAndServe(address, nil)
|
||||
logger.WithError(err).Warning("HTTP server closed.")
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ type Updater struct {
|
||||
type SetFunc func(v interface{})
|
||||
|
||||
// New creates an Updater. Set is called when fields update, using the json
|
||||
// serialized value of Updater's tree. All updates after ctx is canceled are
|
||||
// sererialized value of Updater's tree. All updates after ctx is canceled are
|
||||
// ignored.
|
||||
func New(ctx context.Context, set func([]byte)) *Updater {
|
||||
f := func(v interface{}) {
|
||||
|
@ -12,14 +12,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: custom-eval-tutorial-frontend
|
||||
namespace: custom-eval-tutorial
|
||||
spec:
|
||||
containers:
|
||||
- name: custom-eval-tutorial-frontend
|
||||
image: REGISTRY_PLACEHOLDER/custom-eval-tutorial-frontend:latest
|
||||
imagePullPolicy: Always
|
||||
hostname: custom-eval-tutorial-frontend
|
||||
FROM open-match-base-build as builder
|
||||
|
||||
WORKDIR /go/src/open-match.dev/open-match/examples/evaluator/golang/simple
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o simple .
|
||||
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
WORKDIR /app/
|
||||
COPY --from=builder --chown=nonroot /go/src/open-match.dev/open-match/examples/evaluator/golang/simple/simple /app/
|
||||
|
||||
ENTRYPOINT ["/app/simple"]
|
54
examples/evaluator/golang/simple/evaluate/evaluator.go
Normal file
54
examples/evaluator/golang/simple/evaluate/evaluator.go
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2019 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 evaluate
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/examples"
|
||||
harness "open-match.dev/open-match/pkg/harness/evaluator/golang"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
// Evaluate is where your custom evaluation logic lives.
|
||||
// This sample evaluator sorts and deduplicates the input matches.
|
||||
func Evaluate(p *harness.EvaluatorParams) ([]*pb.Match, error) {
|
||||
scoreInDescendingOrder := func(a, b *pb.Match) bool {
|
||||
return a.GetProperties().GetFields()[examples.MatchScore].GetNumberValue() > b.GetProperties().GetFields()[examples.MatchScore].GetNumberValue()
|
||||
}
|
||||
by(scoreInDescendingOrder).Sort(p.Matches)
|
||||
|
||||
results := []*pb.Match{}
|
||||
dedup := map[string]bool{}
|
||||
|
||||
for _, match := range p.Matches {
|
||||
if isNonCollidingMatch(match, dedup) {
|
||||
for _, ticket := range match.GetTickets() {
|
||||
dedup[ticket.GetId()] = true
|
||||
}
|
||||
results = append(results, match)
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func isNonCollidingMatch(match *pb.Match, validTickets map[string]bool) bool {
|
||||
for _, ticket := range match.GetTickets() {
|
||||
id := ticket.GetId()
|
||||
if _, ok := validTickets[id]; ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
105
examples/evaluator/golang/simple/evaluate/evaluator_test.go
Normal file
105
examples/evaluator/golang/simple/evaluate/evaluator_test.go
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright 2019 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 evaluate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"open-match.dev/open-match/examples"
|
||||
harness "open-match.dev/open-match/pkg/harness/evaluator/golang"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
"open-match.dev/open-match/pkg/structs"
|
||||
)
|
||||
|
||||
func TestEvaluate(t *testing.T) {
|
||||
ticket1 := &pb.Ticket{Id: "1"}
|
||||
ticket2 := &pb.Ticket{Id: "2"}
|
||||
ticket3 := &pb.Ticket{Id: "3"}
|
||||
|
||||
ticket12Score1 := &pb.Match{
|
||||
Tickets: []*pb.Ticket{ticket1, ticket2},
|
||||
Properties: structs.Struct{
|
||||
examples.MatchScore: structs.Number(1),
|
||||
}.S(),
|
||||
}
|
||||
|
||||
ticket12Score10 := &pb.Match{
|
||||
Tickets: []*pb.Ticket{ticket2, ticket1},
|
||||
Properties: structs.Struct{
|
||||
examples.MatchScore: structs.Number(10),
|
||||
}.S(),
|
||||
}
|
||||
|
||||
ticket123Score5 := &pb.Match{
|
||||
Tickets: []*pb.Ticket{ticket1, ticket2, ticket3},
|
||||
Properties: structs.Struct{
|
||||
examples.MatchScore: structs.Number(5),
|
||||
}.S(),
|
||||
}
|
||||
|
||||
ticket3Score50 := &pb.Match{
|
||||
Tickets: []*pb.Ticket{ticket3},
|
||||
Properties: structs.Struct{
|
||||
examples.MatchScore: structs.Number(50),
|
||||
}.S(),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
description string
|
||||
testMatches []*pb.Match
|
||||
wantMatches []*pb.Match
|
||||
}{
|
||||
{
|
||||
description: "test empty request returns empty response",
|
||||
testMatches: []*pb.Match{},
|
||||
wantMatches: []*pb.Match{},
|
||||
},
|
||||
{
|
||||
description: "test input matches output when receiving one match",
|
||||
testMatches: []*pb.Match{ticket12Score1},
|
||||
wantMatches: []*pb.Match{ticket12Score1},
|
||||
},
|
||||
{
|
||||
description: "test deduplicates and expect the one with higher score",
|
||||
testMatches: []*pb.Match{ticket12Score1, ticket12Score10},
|
||||
wantMatches: []*pb.Match{ticket12Score10},
|
||||
},
|
||||
{
|
||||
description: "test first returns matches with higher score",
|
||||
testMatches: []*pb.Match{ticket123Score5, ticket12Score10},
|
||||
wantMatches: []*pb.Match{ticket12Score10},
|
||||
},
|
||||
{
|
||||
description: "test evaluator returns two matches with the highest score",
|
||||
testMatches: []*pb.Match{ticket12Score1, ticket12Score10, ticket123Score5, ticket3Score50},
|
||||
wantMatches: []*pb.Match{ticket12Score10, ticket3Score50},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
gotMatches, err := Evaluate(&harness.EvaluatorParams{Matches: test.testMatches})
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(test.wantMatches), len(gotMatches))
|
||||
for _, match := range gotMatches {
|
||||
assert.Contains(t, test.wantMatches, match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
53
examples/evaluator/golang/simple/evaluate/sort_match.go
Normal file
53
examples/evaluator/golang/simple/evaluate/sort_match.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2019 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 evaluate
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
// by is the type of a "less" function that defines the ordering of its Planet arguments.
|
||||
type by func(p1, p2 *pb.Match) bool
|
||||
|
||||
// matchSorter joins a By function and a slice of Matches to be sorted.
|
||||
type matchSorter struct {
|
||||
matches []*pb.Match
|
||||
by func(a, b *pb.Match) bool // Closure used in the Less method.
|
||||
}
|
||||
|
||||
// Sort is a method on the function type, By, that sorts the argument slice according to the function.
|
||||
func (by by) Sort(matches []*pb.Match) {
|
||||
sort.Sort(&matchSorter{
|
||||
matches: matches,
|
||||
by: by, // The Sort method's receiver is the function (closure) that defines the sort order.
|
||||
})
|
||||
}
|
||||
|
||||
// Len is part of sort.Interface.
|
||||
func (s *matchSorter) Len() int {
|
||||
return len(s.matches)
|
||||
}
|
||||
|
||||
// Swap is part of sort.Interface.
|
||||
func (s *matchSorter) Swap(i, j int) {
|
||||
s.matches[i], s.matches[j] = s.matches[j], s.matches[i]
|
||||
}
|
||||
|
||||
// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
|
||||
func (s *matchSorter) Less(i, j int) bool {
|
||||
return s.by(s.matches[i], s.matches[j])
|
||||
}
|
@ -14,14 +14,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/tutorials/matchmaker101/evaluator/evaluate"
|
||||
)
|
||||
|
||||
const (
|
||||
// Replace this with the port on which your Evaluator service is exposed.
|
||||
evaluatorPort = 50508
|
||||
simple "open-match.dev/open-match/examples/evaluator/golang/simple/evaluate"
|
||||
harness "open-match.dev/open-match/pkg/harness/evaluator/golang"
|
||||
)
|
||||
|
||||
func main() {
|
||||
evaluate.Start(evaluatorPort)
|
||||
// Invoke the harness to setup a GRPC service that handles requests to run the evaluator.
|
||||
harness.RunEvaluator(simple.Evaluate)
|
||||
}
|
@ -1,297 +0,0 @@
|
||||
// Copyright 2020 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 mmf provides a sample match function that uses the GRPC harness to set up 1v1 matches.
|
||||
// This sample is a reference to demonstrate the usage of backfill and should only be used as
|
||||
// a starting point for your match function. You will need to modify the
|
||||
// matchmaking logic in this function based on your game's requirements.
|
||||
package mmf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/golang/protobuf/ptypes/any"
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
"google.golang.org/grpc"
|
||||
"open-match.dev/open-match/pkg/matchfunction"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
const (
|
||||
playersPerMatch = 2
|
||||
openSlotsKey = "open-slots"
|
||||
matchName = "backfill-matchfunction"
|
||||
)
|
||||
|
||||
// matchFunctionService implements pb.MatchFunctionServer, the server generated
|
||||
// by compiling the protobuf, by fulfilling the pb.MatchFunctionServer interface.
|
||||
type matchFunctionService struct {
|
||||
grpc *grpc.Server
|
||||
queryServiceClient pb.QueryServiceClient
|
||||
port int
|
||||
}
|
||||
|
||||
func (s *matchFunctionService) Run(req *pb.RunRequest, stream pb.MatchFunction_RunServer) error {
|
||||
log.Printf("Generating proposals for function %v", req.GetProfile().GetName())
|
||||
|
||||
var proposals []*pb.Match
|
||||
profile := req.GetProfile()
|
||||
pools := profile.GetPools()
|
||||
|
||||
for _, p := range pools {
|
||||
tickets, err := matchfunction.QueryPool(stream.Context(), s.queryServiceClient, p)
|
||||
if err != nil {
|
||||
log.Printf("Failed to query tickets for the given pool, got %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
backfills, err := matchfunction.QueryBackfillPool(stream.Context(), s.queryServiceClient, p)
|
||||
if err != nil {
|
||||
log.Printf("Failed to query backfills for the given pool, got %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
matches, err := makeMatches(profile, p, tickets, backfills)
|
||||
if err != nil {
|
||||
log.Printf("Failed to generate matches, got %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
proposals = append(proposals, matches...)
|
||||
}
|
||||
|
||||
log.Printf("Streaming %v proposals to Open Match", len(proposals))
|
||||
// Stream the generated proposals back to Open Match.
|
||||
for _, proposal := range proposals {
|
||||
if err := stream.Send(&pb.RunResponse{Proposal: proposal}); err != nil {
|
||||
log.Printf("Failed to stream proposals to Open Match, got %s", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeMatches tries to handle backfills at first, then it makes full matches, at the end it makes a match with backfill
|
||||
// if tickets left
|
||||
func makeMatches(profile *pb.MatchProfile, pool *pb.Pool, tickets []*pb.Ticket, backfills []*pb.Backfill) ([]*pb.Match, error) {
|
||||
var matches []*pb.Match
|
||||
newMatches, remainingTickets, err := handleBackfills(profile, tickets, backfills, len(matches))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matches = append(matches, newMatches...)
|
||||
newMatches, remainingTickets = makeFullMatches(profile, remainingTickets, len(matches))
|
||||
matches = append(matches, newMatches...)
|
||||
|
||||
if len(remainingTickets) > 0 {
|
||||
match, err := makeMatchWithBackfill(profile, pool, remainingTickets, len(matches))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matches = append(matches, match)
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// handleBackfills looks at each backfill's openSlots which is a number of required tickets,
|
||||
// acquires that tickets, decreases openSlots in backfill and makes a match with updated backfill and associated tickets.
|
||||
func handleBackfills(profile *pb.MatchProfile, tickets []*pb.Ticket, backfills []*pb.Backfill, lastMatchId int) ([]*pb.Match, []*pb.Ticket, error) {
|
||||
matchId := lastMatchId
|
||||
var matches []*pb.Match
|
||||
|
||||
for _, b := range backfills {
|
||||
openSlots, err := getOpenSlots(b)
|
||||
if err != nil {
|
||||
return nil, tickets, err
|
||||
}
|
||||
|
||||
var matchTickets []*pb.Ticket
|
||||
for openSlots > 0 && len(tickets) > 0 {
|
||||
matchTickets = append(matchTickets, tickets[0])
|
||||
tickets = tickets[1:]
|
||||
openSlots--
|
||||
}
|
||||
|
||||
if len(matchTickets) > 0 {
|
||||
err := setOpenSlots(b, openSlots)
|
||||
if err != nil {
|
||||
return nil, tickets, err
|
||||
}
|
||||
|
||||
matchId++
|
||||
match := newMatch(matchId, profile.Name, matchTickets, b)
|
||||
matches = append(matches, &match)
|
||||
}
|
||||
}
|
||||
|
||||
return matches, tickets, nil
|
||||
}
|
||||
|
||||
// makeMatchWithBackfill makes not full match, creates backfill for it with openSlots = playersPerMatch-len(tickets).
|
||||
func makeMatchWithBackfill(profile *pb.MatchProfile, pool *pb.Pool, tickets []*pb.Ticket, lastMatchId int) (*pb.Match, error) {
|
||||
if len(tickets) == 0 {
|
||||
return nil, fmt.Errorf("tickets are required")
|
||||
}
|
||||
|
||||
if len(tickets) >= playersPerMatch {
|
||||
return nil, fmt.Errorf("too many tickets")
|
||||
}
|
||||
|
||||
matchId := lastMatchId
|
||||
searchFields := newSearchFields(pool)
|
||||
backfill, err := newBackfill(searchFields, playersPerMatch-len(tickets))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matchId++
|
||||
match := newMatch(matchId, profile.Name, tickets, backfill)
|
||||
// indicates that it is a new match and new game server should be allocated for it
|
||||
match.AllocateGameserver = true
|
||||
|
||||
return &match, nil
|
||||
}
|
||||
|
||||
// makeFullMatches makes matches without backfill
|
||||
func makeFullMatches(profile *pb.MatchProfile, tickets []*pb.Ticket, lastMatchId int) ([]*pb.Match, []*pb.Ticket) {
|
||||
ticketNum := 0
|
||||
matchId := lastMatchId
|
||||
var matches []*pb.Match
|
||||
|
||||
for ticketNum < playersPerMatch && len(tickets) >= playersPerMatch {
|
||||
ticketNum++
|
||||
|
||||
if ticketNum == playersPerMatch {
|
||||
matchId++
|
||||
|
||||
match := newMatch(matchId, profile.Name, tickets[:playersPerMatch], nil)
|
||||
matches = append(matches, &match)
|
||||
|
||||
tickets = tickets[playersPerMatch:]
|
||||
ticketNum = 0
|
||||
}
|
||||
}
|
||||
|
||||
return matches, tickets
|
||||
}
|
||||
|
||||
// newSearchFields creates search fields based on pool's search criteria. This is just example of how it can be done.
|
||||
func newSearchFields(pool *pb.Pool) *pb.SearchFields {
|
||||
searchFields := pb.SearchFields{}
|
||||
rangeFilters := pool.GetDoubleRangeFilters()
|
||||
|
||||
if rangeFilters != nil {
|
||||
doubleArgs := make(map[string]float64)
|
||||
for _, f := range rangeFilters {
|
||||
doubleArgs[f.DoubleArg] = (f.Max - f.Min) / 2
|
||||
}
|
||||
|
||||
if len(doubleArgs) > 0 {
|
||||
searchFields.DoubleArgs = doubleArgs
|
||||
}
|
||||
}
|
||||
|
||||
stringFilters := pool.GetStringEqualsFilters()
|
||||
|
||||
if stringFilters != nil {
|
||||
stringArgs := make(map[string]string)
|
||||
for _, f := range stringFilters {
|
||||
stringArgs[f.StringArg] = f.Value
|
||||
}
|
||||
|
||||
if len(stringArgs) > 0 {
|
||||
searchFields.StringArgs = stringArgs
|
||||
}
|
||||
}
|
||||
|
||||
tagFilters := pool.GetTagPresentFilters()
|
||||
|
||||
if tagFilters != nil {
|
||||
tags := make([]string, len(tagFilters))
|
||||
for _, f := range tagFilters {
|
||||
tags = append(tags, f.Tag)
|
||||
}
|
||||
|
||||
if len(tags) > 0 {
|
||||
searchFields.Tags = tags
|
||||
}
|
||||
}
|
||||
|
||||
return &searchFields
|
||||
}
|
||||
|
||||
func newBackfill(searchFields *pb.SearchFields, openSlots int) (*pb.Backfill, error) {
|
||||
b := pb.Backfill{
|
||||
SearchFields: searchFields,
|
||||
Generation: 0,
|
||||
CreateTime: ptypes.TimestampNow(),
|
||||
}
|
||||
|
||||
err := setOpenSlots(&b, int32(openSlots))
|
||||
return &b, err
|
||||
}
|
||||
|
||||
func newMatch(num int, profile string, tickets []*pb.Ticket, b *pb.Backfill) pb.Match {
|
||||
t := time.Now().Format("2006-01-02T15:04:05.00")
|
||||
|
||||
return pb.Match{
|
||||
MatchId: fmt.Sprintf("profile-%s-time-%s-num-%d", profile, t, num),
|
||||
MatchProfile: profile,
|
||||
MatchFunction: matchName,
|
||||
Tickets: tickets,
|
||||
Backfill: b,
|
||||
}
|
||||
}
|
||||
|
||||
func setOpenSlots(b *pb.Backfill, val int32) error {
|
||||
if b.Extensions == nil {
|
||||
b.Extensions = make(map[string]*any.Any)
|
||||
}
|
||||
|
||||
any, err := ptypes.MarshalAny(&wrappers.Int32Value{Value: val})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Extensions[openSlotsKey] = any
|
||||
return nil
|
||||
}
|
||||
|
||||
func getOpenSlots(b *pb.Backfill) (int32, error) {
|
||||
if b == nil {
|
||||
return 0, fmt.Errorf("expected backfill is not nil")
|
||||
}
|
||||
|
||||
if b.Extensions != nil {
|
||||
if any, ok := b.Extensions[openSlotsKey]; ok {
|
||||
var val wrappers.Int32Value
|
||||
err := ptypes.UnmarshalAny(any, &val)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return val.Value, nil
|
||||
}
|
||||
}
|
||||
|
||||
return playersPerMatch, nil
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
// Copyright 2020 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 mmf
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/golang/protobuf/ptypes/any"
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
"github.com/stretchr/testify/require"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
func TestHandleBackfills(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
tickets []*pb.Ticket
|
||||
backfills []*pb.Backfill
|
||||
lastMatchId int
|
||||
expectedMatchLen int
|
||||
expectedTicketLen int
|
||||
expectedOpenSlots int32
|
||||
expectedErr bool
|
||||
}{
|
||||
{name: "returns no matches when no backfills specified", expectedMatchLen: 0, expectedTicketLen: 0},
|
||||
{name: "returns no matches when no tickets specified", expectedMatchLen: 0, expectedTicketLen: 0},
|
||||
{name: "returns a match with open slots decreased", tickets: []*pb.Ticket{{Id: "1"}}, backfills: []*pb.Backfill{withOpenSlots(1)}, expectedMatchLen: 1, expectedTicketLen: 0, expectedOpenSlots: playersPerMatch - 2},
|
||||
} {
|
||||
testCase := tc
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
profile := pb.MatchProfile{Name: "matchProfile"}
|
||||
matches, tickets, err := handleBackfills(&profile, testCase.tickets, testCase.backfills, testCase.lastMatchId)
|
||||
require.Equal(t, testCase.expectedErr, err != nil)
|
||||
require.Equal(t, testCase.expectedTicketLen, len(tickets))
|
||||
|
||||
if err != nil {
|
||||
require.Equal(t, 0, len(matches))
|
||||
} else {
|
||||
for _, m := range matches {
|
||||
require.NotNil(t, m.Backfill)
|
||||
|
||||
openSlots, err := getOpenSlots(m.Backfill)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testCase.expectedOpenSlots, openSlots)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeMatchWithBackfill(t *testing.T) {
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
tickets []*pb.Ticket
|
||||
lastMatchId int
|
||||
expectedOpenSlots int32
|
||||
expectedErr bool
|
||||
}{
|
||||
{name: "returns an error when length of tickets is greater then playerPerMatch", tickets: []*pb.Ticket{{Id: "1"}, {Id: "2"}, {Id: "3"}, {Id: "4"}, {Id: "5"}}, expectedErr: true},
|
||||
{name: "returns an error when length of tickets is equal to playerPerMatch", tickets: []*pb.Ticket{{Id: "1"}, {Id: "2"}, {Id: "3"}, {Id: "4"}}, expectedErr: true},
|
||||
{name: "returns an error when no tickets are provided", expectedErr: true},
|
||||
{name: "returns a match with backfill", tickets: []*pb.Ticket{{Id: "1"}}, expectedOpenSlots: playersPerMatch - 1},
|
||||
} {
|
||||
testCase := testCase
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool := pb.Pool{}
|
||||
profile := pb.MatchProfile{Name: "matchProfile"}
|
||||
match, err := makeMatchWithBackfill(&profile, &pool, testCase.tickets, testCase.lastMatchId)
|
||||
require.Equal(t, testCase.expectedErr, err != nil)
|
||||
|
||||
if err == nil {
|
||||
require.NotNil(t, match)
|
||||
require.NotNil(t, match.Backfill)
|
||||
require.True(t, match.AllocateGameserver)
|
||||
require.Equal(t, "", match.Backfill.Id)
|
||||
|
||||
openSlots, err := getOpenSlots(match.Backfill)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, testCase.expectedOpenSlots, openSlots)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeFullMatches(t *testing.T) {
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
tickets []*pb.Ticket
|
||||
lastMatchId int
|
||||
expectedMatchLen int
|
||||
expectedTicketLen int
|
||||
}{
|
||||
{name: "returns no matches when there are no tickets", tickets: []*pb.Ticket{}, expectedMatchLen: 0, expectedTicketLen: 0},
|
||||
{name: "returns no matches when length of tickets is less then playersPerMatch", tickets: []*pb.Ticket{{Id: "1"}}, expectedMatchLen: 0, expectedTicketLen: 1},
|
||||
{name: "returns a match when length of tickets is greater then playersPerMatch", tickets: []*pb.Ticket{{Id: "1"}, {Id: "2"}}, expectedMatchLen: 1, expectedTicketLen: 0},
|
||||
} {
|
||||
testCase := testCase
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
profile := pb.MatchProfile{Name: "matchProfile"}
|
||||
matches, tickets := makeFullMatches(&profile, testCase.tickets, testCase.lastMatchId)
|
||||
|
||||
require.Equal(t, testCase.expectedMatchLen, len(matches))
|
||||
require.Equal(t, testCase.expectedTicketLen, len(tickets))
|
||||
|
||||
for _, m := range matches {
|
||||
require.Nil(t, m.Backfill)
|
||||
require.Equal(t, playersPerMatch, len(m.Tickets))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func withOpenSlots(openSlots int) *pb.Backfill {
|
||||
val, err := ptypes.MarshalAny(&wrappers.Int32Value{Value: int32(openSlots)})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &pb.Backfill{
|
||||
Extensions: map[string]*any.Any{
|
||||
openSlotsKey: val,
|
||||
},
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
// Copyright 2020 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 mmf provides a sample match function that uses the GRPC harness to set up 1v1 matches.
|
||||
// This sample is a reference to demonstrate the usage of backfill and should only be used as
|
||||
// a starting point for your match function. You will need to modify the
|
||||
// matchmaking logic in this function based on your game's requirements.
|
||||
package mmf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
func Start(queryServiceAddr string, serverPort int) {
|
||||
// Connect to QueryService.
|
||||
conn, err := grpc.Dial(queryServiceAddr, grpc.WithInsecure())
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to Open Match, got %s", err.Error())
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
mmfService := matchFunctionService{
|
||||
queryServiceClient: pb.NewQueryServiceClient(conn),
|
||||
}
|
||||
|
||||
// Create and host a new gRPC service on the configured port.
|
||||
server := grpc.NewServer()
|
||||
pb.RegisterMatchFunctionServer(server, &mmfService)
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", serverPort))
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("TCP net listener initialization failed for port %v, got %s", serverPort, err.Error())
|
||||
}
|
||||
|
||||
log.Printf("TCP net listener initialized for port %v", serverPort)
|
||||
err = server.Serve(ln)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("gRPC serve failed, got %s", err.Error())
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
# Copyright 2020 Google LLC
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -14,11 +14,11 @@
|
||||
|
||||
FROM open-match-base-build as builder
|
||||
|
||||
WORKDIR /go/src/open-match.dev/open-match/examples/functions/golang/backfill
|
||||
WORKDIR /go/src/open-match.dev/open-match/examples/functions/golang/pool
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o matchfunction .
|
||||
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
WORKDIR /app/
|
||||
COPY --from=builder --chown=nonroot /go/src/open-match.dev/open-match/examples/functions/golang/backfill/matchfunction /app/
|
||||
COPY --from=builder --chown=nonroot /go/src/open-match.dev/open-match/examples/functions/golang/pool/matchfunction /app/
|
||||
|
||||
ENTRYPOINT ["/app/matchfunction"]
|
||||
ENTRYPOINT ["/app/matchfunction"]
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package main defines a sample match function that uses the GRPC harness to set up
|
||||
// Package main a sample match function that uses the GRPC harness to set up
|
||||
// the match making function as a service. This sample is a reference
|
||||
// to demonstrate the usage of the GRPC harness and should only be used as
|
||||
// a starting point for your match function. You will need to modify the
|
||||
@ -20,18 +20,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/tutorials/matchmaker101/matchfunction/mmf"
|
||||
)
|
||||
|
||||
// This tutorial implenents a basic Match Function that is hosted in the below
|
||||
// configured port. You can also configure the Open Match QueryService endpoint
|
||||
// with which the Match Function communicates to query the Tickets.
|
||||
|
||||
const (
|
||||
queryServiceAddress = "open-match-query.open-match.svc.cluster.local:50503" // Address of the QueryService endpoint.
|
||||
serverPort = 50502 // The port for hosting the Match Function.
|
||||
pool "open-match.dev/open-match/examples/functions/golang/pool/mmf"
|
||||
mmfHarness "open-match.dev/open-match/pkg/harness/function/golang"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mmf.Start(queryServiceAddress, serverPort)
|
||||
// Invoke the harness to setup a GRPC service that handles requests to run the
|
||||
// match function. The harness itself queries open match for player pools for
|
||||
// the specified request and passes the pools to the match function to generate
|
||||
// proposals.
|
||||
mmfHarness.RunMatchFunction(&mmfHarness.FunctionSettings{
|
||||
Func: pool.MakeMatches,
|
||||
})
|
||||
}
|
74
examples/functions/golang/pool/mmf/matchfunction.go
Normal file
74
examples/functions/golang/pool/mmf/matchfunction.go
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2019 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 mmf provides a sample match function that uses the GRPC harness to set up
|
||||
// the match making function as a service. This sample is a reference
|
||||
// to demonstrate the usage of the GRPC harness and should only be used as
|
||||
// a starting point for your match function. You will need to modify the
|
||||
// matchmaking logic in this function based on your game's requirements.
|
||||
package mmf
|
||||
|
||||
import (
|
||||
"github.com/rs/xid"
|
||||
"open-match.dev/open-match/examples"
|
||||
mmfHarness "open-match.dev/open-match/pkg/harness/function/golang"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
"open-match.dev/open-match/pkg/structs"
|
||||
)
|
||||
|
||||
var (
|
||||
matchName = "pool-based-match"
|
||||
)
|
||||
|
||||
// MakeMatches is where your custom matchmaking logic lives.
|
||||
// This is the core match making function that will be triggered by Open Match to generate matches.
|
||||
// The goal of this function is to generate predictable matches that can be validated without flakyness.
|
||||
// This match function loops through all the pools and generates one match per pool aggregating all players
|
||||
// in that pool in the generated match.
|
||||
func MakeMatches(params *mmfHarness.MatchFunctionParams) ([]*pb.Match, error) {
|
||||
var result []*pb.Match
|
||||
for pool, tickets := range params.PoolNameToTickets {
|
||||
if len(tickets) != 0 {
|
||||
roster := &pb.Roster{Name: pool}
|
||||
|
||||
for _, ticket := range tickets {
|
||||
roster.TicketIds = append(roster.GetTicketIds(), ticket.GetId())
|
||||
}
|
||||
|
||||
result = append(result, &pb.Match{
|
||||
MatchId: xid.New().String(),
|
||||
MatchProfile: params.ProfileName,
|
||||
MatchFunction: matchName,
|
||||
Tickets: tickets,
|
||||
Rosters: []*pb.Roster{roster},
|
||||
Properties: structs.Struct{
|
||||
examples.MatchScore: structs.Number(scoreCalculator(tickets)),
|
||||
}.S(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// This match function defines the quality of a match as the sum of the attribute values of all tickets per match
|
||||
func scoreCalculator(tickets []*pb.Ticket) float64 {
|
||||
matchScore := 0.0
|
||||
for _, ticket := range tickets {
|
||||
for _, v := range ticket.GetProperties().GetFields() {
|
||||
matchScore += v.GetNumberValue()
|
||||
}
|
||||
}
|
||||
return matchScore
|
||||
}
|
109
examples/functions/golang/pool/mmf/matchfunction_test.go
Normal file
109
examples/functions/golang/pool/mmf/matchfunction_test.go
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright 2019 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 mmf
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"open-match.dev/open-match/examples"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
mmfHarness "open-match.dev/open-match/pkg/harness/function/golang"
|
||||
"open-match.dev/open-match/pkg/structs"
|
||||
)
|
||||
|
||||
func TestMakeMatches(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tickets := []*pb.Ticket{
|
||||
{
|
||||
Id: "1",
|
||||
Properties: structs.Struct{
|
||||
"level": structs.Number(10),
|
||||
"defense": structs.Number(100),
|
||||
}.S(),
|
||||
},
|
||||
{
|
||||
Id: "2",
|
||||
Properties: structs.Struct{
|
||||
"level": structs.Number(10),
|
||||
"attack": structs.Number(50),
|
||||
}.S(),
|
||||
},
|
||||
{
|
||||
Id: "3",
|
||||
Properties: structs.Struct{
|
||||
"level": structs.Number(10),
|
||||
"speed": structs.Number(522),
|
||||
}.S(),
|
||||
}, {
|
||||
Id: "4",
|
||||
Properties: structs.Struct{
|
||||
"level": structs.Number(10),
|
||||
"mana": structs.Number(1),
|
||||
}.S(),
|
||||
},
|
||||
}
|
||||
|
||||
poolNameToTickets := map[string][]*pb.Ticket{
|
||||
"pool1": tickets[:2],
|
||||
"pool2": tickets[2:],
|
||||
}
|
||||
|
||||
p := &mmfHarness.MatchFunctionParams{
|
||||
Logger: &logrus.Entry{},
|
||||
ProfileName: "test-profile",
|
||||
Rosters: []*pb.Roster{},
|
||||
PoolNameToTickets: poolNameToTickets,
|
||||
}
|
||||
|
||||
matches, err := MakeMatches(p)
|
||||
assert.Nil(err)
|
||||
assert.Equal(len(matches), 2)
|
||||
|
||||
actual := []*pb.Match{}
|
||||
for _, match := range matches {
|
||||
actual = append(actual, &pb.Match{
|
||||
MatchProfile: match.MatchProfile,
|
||||
MatchFunction: match.MatchFunction,
|
||||
Tickets: match.Tickets,
|
||||
Rosters: match.Rosters,
|
||||
Properties: match.Properties,
|
||||
})
|
||||
}
|
||||
|
||||
matchGen := func(poolName string, tickets []*pb.Ticket) *pb.Match {
|
||||
tids := []string{}
|
||||
for _, ticket := range tickets {
|
||||
tids = append(tids, ticket.GetId())
|
||||
}
|
||||
|
||||
return &pb.Match{
|
||||
MatchProfile: p.ProfileName,
|
||||
MatchFunction: matchName,
|
||||
Tickets: tickets,
|
||||
Rosters: []*pb.Roster{{Name: poolName, TicketIds: tids}},
|
||||
Properties: structs.Struct{
|
||||
examples.MatchScore: structs.Number(scoreCalculator(tickets)),
|
||||
}.S(),
|
||||
}
|
||||
}
|
||||
|
||||
for poolName, tickets := range poolNameToTickets {
|
||||
assert.Contains(actual, matchGen(poolName, tickets))
|
||||
}
|
||||
}
|
@ -12,14 +12,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: custom-eval-tutorial-director
|
||||
namespace: custom-eval-tutorial
|
||||
spec:
|
||||
containers:
|
||||
- name: custom-eval-tutorial-director
|
||||
image: REGISTRY_PLACEHOLDER/custom-eval-tutorial-director:latest
|
||||
imagePullPolicy: Always
|
||||
hostname: custom-eval-tutorial-director
|
||||
FROM open-match-base-build as builder
|
||||
|
||||
WORKDIR /go/src/open-match.dev/open-match/examples/functions/golang/rosterbased
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o matchfunction .
|
||||
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
WORKDIR /app/
|
||||
COPY --from=builder --chown=nonroot /go/src/open-match.dev/open-match/examples/functions/golang/rosterbased/matchfunction /app/
|
||||
|
||||
ENTRYPOINT ["/app/matchfunction"]
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2020 Google LLC
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -20,14 +20,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/examples/functions/golang/backfill/mmf"
|
||||
)
|
||||
|
||||
const (
|
||||
queryServiceAddr = "open-match-query.open-match.svc.cluster.local:50503" // Address of the QueryService endpoint.
|
||||
serverPort = 50502 // The port for hosting the Match Function.
|
||||
rosterbased "open-match.dev/open-match/examples/functions/golang/rosterbased/mmf"
|
||||
mmfHarness "open-match.dev/open-match/pkg/harness/function/golang"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mmf.Start(queryServiceAddr, serverPort)
|
||||
// Invoke the harness to setup a GRPC service that handles requests to run the
|
||||
// match function. The harness itself queries open match for player pools for
|
||||
// the specified request and passes the pools to the match function to generate
|
||||
// proposals.
|
||||
mmfHarness.RunMatchFunction(&mmfHarness.FunctionSettings{
|
||||
Func: rosterbased.MakeMatches,
|
||||
})
|
||||
}
|
119
examples/functions/golang/rosterbased/mmf/matchfunction.go
Normal file
119
examples/functions/golang/rosterbased/mmf/matchfunction.go
Normal file
@ -0,0 +1,119 @@
|
||||
// Copyright 2019 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 mmf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
mmfHarness "open-match.dev/open-match/pkg/harness/function/golang"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
var (
|
||||
matchName = "roster-based-matchfunction"
|
||||
emptyRosterSpot = "EMPTY_ROSTER_SPOT"
|
||||
logger = logrus.WithFields(logrus.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "mmf.rosterbased",
|
||||
})
|
||||
)
|
||||
|
||||
func MakeMatches(p *mmfHarness.MatchFunctionParams) ([]*pb.Match, error) {
|
||||
// This roster based match function expects the match profile to have a
|
||||
// populated roster specifying the empty slots for each pool name and also
|
||||
// have the ticket pools referenced in the roster. It generates matches by
|
||||
// populating players from the specified pools into rosters.
|
||||
wantTickets, err := wantPoolTickets(p.Rosters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var matches []*pb.Match
|
||||
for {
|
||||
insufficientTickets := false
|
||||
matchTickets := []*pb.Ticket{}
|
||||
matchRosters := []*pb.Roster{}
|
||||
// Loop through each pool wanted in the rosters and pick the number of
|
||||
// wanted players from the respective Pool.
|
||||
for poolName, want := range wantTickets {
|
||||
have, ok := p.PoolNameToTickets[poolName]
|
||||
if !ok {
|
||||
// A wanted Pool was not found in the Pools specified in the profile.
|
||||
insufficientTickets = true
|
||||
break
|
||||
}
|
||||
|
||||
if len(have) < want {
|
||||
// The Pool in the profile has fewer tickets than what the roster needs.
|
||||
insufficientTickets = true
|
||||
break
|
||||
}
|
||||
|
||||
// Populate the wanted tickets from the Tickets in the corresponding Pool.
|
||||
matchTickets = append(matchTickets, have[0:want]...)
|
||||
p.PoolNameToTickets[poolName] = have[want:]
|
||||
var ids []string
|
||||
for _, ticket := range matchTickets {
|
||||
ids = append(ids, ticket.Id)
|
||||
}
|
||||
|
||||
matchRosters = append(matchRosters, &pb.Roster{
|
||||
Name: poolName,
|
||||
TicketIds: ids,
|
||||
})
|
||||
}
|
||||
|
||||
if insufficientTickets {
|
||||
// Ran out of Tickets. Matches cannot be created from the remaining Tickets.
|
||||
break
|
||||
}
|
||||
|
||||
matches = append(matches, &pb.Match{
|
||||
MatchId: fmt.Sprintf("profile-%v-time-%v", p.ProfileName, time.Now().Format("2006-01-02T15:04:05.00")),
|
||||
MatchProfile: p.ProfileName,
|
||||
MatchFunction: matchName,
|
||||
Tickets: matchTickets,
|
||||
Rosters: matchRosters,
|
||||
})
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// wantPoolTickets parses the roster to return a map of the Pool name to the
|
||||
// number of empty roster slots for that Pool.
|
||||
func wantPoolTickets(rosters []*pb.Roster) (map[string]int, error) {
|
||||
wantTickets := make(map[string]int)
|
||||
for _, r := range rosters {
|
||||
if _, ok := wantTickets[r.Name]; ok {
|
||||
// We do not expect multiple Roster Pools to have the same name.
|
||||
logger.Errorf("multiple rosters with same name not supported")
|
||||
return nil, status.Error(codes.InvalidArgument, "multiple rosters with same name not supported")
|
||||
}
|
||||
|
||||
wantTickets[r.Name] = 0
|
||||
for _, slot := range r.TicketIds {
|
||||
if slot == emptyRosterSpot {
|
||||
wantTickets[r.Name] = wantTickets[r.Name] + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return wantTickets, nil
|
||||
}
|
@ -20,14 +20,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/examples/functions/golang/soloduel/mmf"
|
||||
)
|
||||
|
||||
const (
|
||||
queryServiceAddr = "open-match-query.open-match.svc.cluster.local:50503" // Address of the QueryService endpoint.
|
||||
serverPort = 50502 // The port for hosting the Match Function.
|
||||
soloduel "open-match.dev/open-match/examples/functions/golang/soloduel/mmf"
|
||||
mmfHarness "open-match.dev/open-match/pkg/harness/function/golang"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mmf.Start(queryServiceAddr, serverPort)
|
||||
// Invoke the harness to setup a GRPC service that handles requests to run the
|
||||
// match function. The harness itself queries open match for player pools for
|
||||
// the specified request and passes the pools to the match function to generate
|
||||
// proposals.
|
||||
mmfHarness.RunMatchFunction(&mmfHarness.FunctionSettings{
|
||||
Func: soloduel.MakeMatches,
|
||||
})
|
||||
}
|
||||
|
@ -20,11 +20,9 @@ package mmf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"open-match.dev/open-match/pkg/matchfunction"
|
||||
mmfHarness "open-match.dev/open-match/pkg/harness/function/golang"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
@ -32,17 +30,15 @@ var (
|
||||
matchName = "a-simple-1v1-matchfunction"
|
||||
)
|
||||
|
||||
// matchFunctionService implements pb.MatchFunctionServer, the server generated
|
||||
// by compiling the protobuf, by fulfilling the pb.MatchFunctionServer interface.
|
||||
type matchFunctionService struct {
|
||||
grpc *grpc.Server
|
||||
queryServiceClient pb.QueryServiceClient
|
||||
port int
|
||||
}
|
||||
// MakeMatches is where your custom matchmaking logic lives.
|
||||
func MakeMatches(p *mmfHarness.MatchFunctionParams) ([]*pb.Match, error) {
|
||||
// This simple match function does the following things
|
||||
// 1. Deduplicates the tickets from the pools into a single list.
|
||||
// 2. Groups players into 1v1 matches.
|
||||
|
||||
func makeMatches(poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error) {
|
||||
tickets := map[string]*pb.Ticket{}
|
||||
for _, pool := range poolTickets {
|
||||
|
||||
for _, pool := range p.PoolNameToTickets {
|
||||
for _, ticket := range pool {
|
||||
tickets[ticket.GetId()] = ticket
|
||||
}
|
||||
@ -60,8 +56,8 @@ func makeMatches(poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error) {
|
||||
|
||||
if len(thisMatch) >= 2 {
|
||||
matches = append(matches, &pb.Match{
|
||||
MatchId: fmt.Sprintf("profile-%s-time-%s-num-%d", matchName, t, matchNum),
|
||||
MatchProfile: matchName,
|
||||
MatchId: fmt.Sprintf("profile-%s-time-%s-num-%d", p.ProfileName, t, matchNum),
|
||||
MatchProfile: p.ProfileName,
|
||||
MatchFunction: matchName,
|
||||
Tickets: thisMatch,
|
||||
})
|
||||
@ -73,33 +69,3 @@ func makeMatches(poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error) {
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// Run is this match function's implementation of the gRPC call defined in api/matchfunction.proto.
|
||||
func (s *matchFunctionService) Run(req *pb.RunRequest, stream pb.MatchFunction_RunServer) error {
|
||||
// Fetch tickets for the pools specified in the Match Profile.
|
||||
log.Printf("Generating proposals for function %v", req.GetProfile().GetName())
|
||||
|
||||
poolTickets, err := matchfunction.QueryPools(stream.Context(), s.queryServiceClient, req.GetProfile().GetPools())
|
||||
if err != nil {
|
||||
log.Printf("Failed to query tickets for the given pools, got %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate proposals.
|
||||
proposals, err := makeMatches(poolTickets)
|
||||
if err != nil {
|
||||
log.Printf("Failed to generate matches, got %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Streaming %v proposals to Open Match", len(proposals))
|
||||
// Stream the generated proposals back to Open Match.
|
||||
for _, proposal := range proposals {
|
||||
if err := stream.Send(&pb.RunResponse{Proposal: proposal}); err != nil {
|
||||
log.Printf("Failed to stream proposals to Open Match, got %s", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -19,24 +19,33 @@ import (
|
||||
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
mmfHarness "open-match.dev/open-match/pkg/harness/function/golang"
|
||||
)
|
||||
|
||||
func TestMakeMatchesDeduplicate(t *testing.T) {
|
||||
require := require.New(t)
|
||||
assert := assert.New(t)
|
||||
|
||||
poolNameToTickets := map[string][]*pb.Ticket{
|
||||
"pool1": {{Id: "1"}},
|
||||
"pool2": {{Id: "1"}},
|
||||
}
|
||||
|
||||
matches, err := makeMatches(poolNameToTickets)
|
||||
require.Nil(err)
|
||||
require.Equal(len(matches), 0)
|
||||
p := &mmfHarness.MatchFunctionParams{
|
||||
Logger: &logrus.Entry{},
|
||||
ProfileName: "test-profile",
|
||||
Rosters: []*pb.Roster{},
|
||||
PoolNameToTickets: poolNameToTickets,
|
||||
}
|
||||
|
||||
matches, err := MakeMatches(p)
|
||||
assert.Nil(err)
|
||||
assert.Equal(len(matches), 0)
|
||||
}
|
||||
|
||||
func TestMakeMatches(t *testing.T) {
|
||||
require := require.New(t)
|
||||
assert := assert.New(t)
|
||||
|
||||
poolNameToTickets := map[string][]*pb.Ticket{
|
||||
"pool1": {{Id: "1"}, {Id: "2"}, {Id: "3"}},
|
||||
@ -44,12 +53,21 @@ func TestMakeMatches(t *testing.T) {
|
||||
"pool3": {{Id: "5"}, {Id: "6"}, {Id: "7"}},
|
||||
}
|
||||
|
||||
matches, err := makeMatches(poolNameToTickets)
|
||||
require.Nil(err)
|
||||
require.Equal(len(matches), 3)
|
||||
p := &mmfHarness.MatchFunctionParams{
|
||||
Logger: &logrus.Entry{},
|
||||
ProfileName: "test-profile",
|
||||
Rosters: []*pb.Roster{},
|
||||
PoolNameToTickets: poolNameToTickets,
|
||||
}
|
||||
|
||||
matches, err := MakeMatches(p)
|
||||
assert.Nil(err)
|
||||
assert.Equal(len(matches), 3)
|
||||
|
||||
for _, match := range matches {
|
||||
require.Equal(2, len(match.Tickets))
|
||||
require.Equal(matchName, match.MatchFunction)
|
||||
assert.Equal(2, len(match.Tickets))
|
||||
assert.Equal(matchName, match.MatchFunction)
|
||||
assert.Equal(p.ProfileName, match.MatchProfile)
|
||||
assert.Nil(match.Rosters)
|
||||
}
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
// Copyright 2019 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 mmf provides a sample match function that uses the GRPC harness to set up 1v1 matches.
|
||||
// This sample is a reference to demonstrate the usage of the GRPC harness and should only be used as
|
||||
// a starting point for your match function. You will need to modify the
|
||||
// matchmaking logic in this function based on your game's requirements.
|
||||
package mmf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
// Start creates and starts the Match Function server and also connects to Open
|
||||
// Match's queryService service. This connection is used at runtime to fetch tickets
|
||||
// for pools specified in MatchProfile.
|
||||
func Start(queryServiceAddr string, serverPort int) {
|
||||
// Connect to QueryService.
|
||||
conn, err := grpc.Dial(queryServiceAddr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to Open Match, got %s", err.Error())
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
mmfService := matchFunctionService{
|
||||
queryServiceClient: pb.NewQueryServiceClient(conn),
|
||||
}
|
||||
|
||||
// Create and host a new gRPC service on the configured port.
|
||||
server := grpc.NewServer()
|
||||
pb.RegisterMatchFunctionServer(server, &mmfService)
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", serverPort))
|
||||
if err != nil {
|
||||
log.Fatalf("TCP net listener initialization failed for port %v, got %s", serverPort, err.Error())
|
||||
}
|
||||
|
||||
log.Printf("TCP net listener initialized for port %v", serverPort)
|
||||
err = server.Serve(ln)
|
||||
if err != nil {
|
||||
log.Fatalf("gRPC serve failed, got %s", err.Error())
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
## How to use this framework
|
||||
|
||||
This is the framework that we use to benchmark Open Match against different matchmaking scenarios. For now (02/24/2020), this framework supports a Battle Royale, a Basic 1v1 matchmaking, and a Team Shooter scenario. You are welcome to write up your own `Scenario`, test it, and share the number that you are able to get to us.
|
||||
|
||||
1. The `Scenario` struct under the `scenarios/scenarios.go` file defines the parameters that this framework currently support/plan to support.
|
||||
2. Each subpackage `battleroyal`, `firstmatch`, and `teamshooter` implements to `GameScenario` interface defined under `scenarios/scenarios.go` file. Feel free to write your own benchmark scenario by implementing the interface.
|
||||
- Ticket `func() *pb.Ticket` - Tickets generator
|
||||
- Profiles `func() []*pb.MatchProfile` - Profiles generator
|
||||
- MMF `MatchFunction(p *pb.MatchProfile, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error)` - Custom matchmaking logic using a MatchProfile and a map struct that contains the mapping from pool name to the tickets of that pool.
|
||||
- Evaluate `Evaluate(stream pb.Evaluator_EvaluateServer) error` - Custom logic implementation of the evaluator.
|
||||
|
||||
Follow the instructions below if you want to use any of the existing benchmarking scenarios.
|
||||
|
||||
1. Open the `scenarios.go` file under the scenarios directory.
|
||||
2. Change the value of the `ActiveScenario` variable to the scenario that you would like Open Match to run against.
|
||||
3. Make sure you have `kubectl` connected to an existing Kubernetes cluster and run `make push-images` followed by `make install-scale-chart` to push the images and install Open Match core along with the scale components in the cluster.
|
||||
4. Run `make proxy`
|
||||
- Open `localhost:3000` to see the Grafana dashboards.
|
||||
- Open `localhost:9090` to see the Prometheus query server.
|
||||
- Open `localhost:[COMPONENT_HTTP_ENDPOINT]/help` to see how to access the zpages.
|
@ -20,15 +20,14 @@ import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
"open-match.dev/open-match/examples/scale/scenarios"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
"open-match.dev/open-match/examples/scale/profiles"
|
||||
"open-match.dev/open-match/internal/config"
|
||||
"open-match.dev/open-match/internal/logging"
|
||||
"open-match.dev/open-match/internal/rpc"
|
||||
"open-match.dev/open-match/internal/telemetry"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
@ -38,35 +37,30 @@ var (
|
||||
"component": "scale.backend",
|
||||
})
|
||||
|
||||
activeScenario = scenarios.ActiveScenario
|
||||
|
||||
mIterations = telemetry.Counter("scale_backend_iterations", "fetch match iterations")
|
||||
mFetchMatchCalls = telemetry.Counter("scale_backend_fetch_match_calls", "fetch match calls")
|
||||
mFetchMatchSuccesses = telemetry.Counter("scale_backend_fetch_match_successes", "fetch match successes")
|
||||
mFetchMatchErrors = telemetry.Counter("scale_backend_fetch_match_errors", "fetch match errors")
|
||||
mMatchesReturned = telemetry.Counter("scale_backend_matches_returned", "matches returned")
|
||||
mSumTicketsReturned = telemetry.Counter("scale_backend_sum_tickets_returned", "tickets in matches returned")
|
||||
mMatchesAssigned = telemetry.Counter("scale_backend_matches_assigned", "matches assigned")
|
||||
mMatchAssignsFailed = telemetry.Counter("scale_backend_match_assigns_failed", "match assigns failed")
|
||||
mBackfillsDeleted = telemetry.Counter("scale_backend_backfills_deleted", "backfills deleted")
|
||||
mBackfillDeletesFailed = telemetry.Counter("scale_backend_backfill_deletes_failed", "backfill deletes failed")
|
||||
// TODO: Add metrics to track matches created, tickets assigned, deleted.
|
||||
matchCount uint64
|
||||
assigned uint64
|
||||
deleted uint64
|
||||
)
|
||||
|
||||
// Run triggers execution of functions that continuously fetch, assign and
|
||||
// delete matches.
|
||||
func BindService(p *appmain.Params, b *appmain.Bindings) error {
|
||||
go run(p.Config())
|
||||
return nil
|
||||
}
|
||||
func Run() {
|
||||
cfg, err := config.Read()
|
||||
if err != nil {
|
||||
logger.WithFields(logrus.Fields{
|
||||
"error": err.Error(),
|
||||
}).Fatalf("cannot read configuration.")
|
||||
}
|
||||
|
||||
func run(cfg config.View) {
|
||||
logging.ConfigureLogging(cfg)
|
||||
beConn, err := rpc.GRPCClientFromConfig(cfg, "api.backend")
|
||||
if err != nil {
|
||||
logger.Fatalf("failed to connect to Open Match Backend, got %v", err)
|
||||
}
|
||||
|
||||
defer beConn.Close()
|
||||
be := pb.NewBackendServiceClient(beConn)
|
||||
be := pb.NewBackendClient(beConn)
|
||||
|
||||
feConn, err := rpc.GRPCClientFromConfig(cfg, "api.frontend")
|
||||
if err != nil {
|
||||
@ -74,178 +68,116 @@ func run(cfg config.View) {
|
||||
}
|
||||
|
||||
defer feConn.Close()
|
||||
fe := pb.NewFrontendServiceClient(feConn)
|
||||
fe := pb.NewFrontendClient(feConn)
|
||||
|
||||
w := logger.Writer()
|
||||
defer w.Close()
|
||||
// The buffered channels attempt to decouple fetch, assign and delete. It is
|
||||
// best effort and these operations may still block each other if buffers are full.
|
||||
matches := make(chan *pb.Match, 1000)
|
||||
deleteIds := make(chan string, 1000)
|
||||
|
||||
matchesToAssign := make(chan *pb.Match, 30000)
|
||||
go doFetch(cfg, be, matches)
|
||||
go doAssign(be, matches, deleteIds)
|
||||
go doDelete(fe, deleteIds)
|
||||
|
||||
if activeScenario.BackendAssignsTickets {
|
||||
for i := 0; i < 100; i++ {
|
||||
go runAssignments(be, matchesToAssign)
|
||||
}
|
||||
}
|
||||
// The above goroutines run forever and so the main goroutine needs to block.
|
||||
select {}
|
||||
}
|
||||
|
||||
backfillsToDelete := make(chan *pb.Backfill, 30000)
|
||||
|
||||
if activeScenario.BackendDeletesBackfills {
|
||||
for i := 0; i < 100; i++ {
|
||||
go runDeleteBackfills(fe, backfillsToDelete)
|
||||
}
|
||||
}
|
||||
|
||||
matchesToAcknowledge := make(chan *pb.Match, 30000)
|
||||
|
||||
if activeScenario.BackendAcknowledgesBackfills {
|
||||
for i := 0; i < 100; i++ {
|
||||
go runAcknowledgeBackfills(fe, matchesToAcknowledge, backfillsToDelete)
|
||||
}
|
||||
}
|
||||
|
||||
// Don't go faster than this, as it likely means that FetchMatches is throwing
|
||||
// errors, and will continue doing so if queried very quickly.
|
||||
for range time.Tick(time.Millisecond * 250) {
|
||||
// Keep pulling matches from Open Match backend
|
||||
profiles := activeScenario.Profiles()
|
||||
// doFetch continuously fetches all profiles in a loop and queues up the fetched
|
||||
// matches for assignment.
|
||||
func doFetch(cfg config.View, be pb.BackendClient, matches chan *pb.Match) {
|
||||
startTime := time.Now()
|
||||
mprofiles := profiles.Generate(cfg)
|
||||
for {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, p := range profiles {
|
||||
for _, p := range mprofiles {
|
||||
wg.Add(1)
|
||||
go func(wg *sync.WaitGroup, p *pb.MatchProfile) {
|
||||
p := p
|
||||
go func(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
runFetchMatches(be, p, matchesToAssign, matchesToAcknowledge)
|
||||
}(&wg, p)
|
||||
fetch(be, p, matches)
|
||||
}(&wg)
|
||||
}
|
||||
|
||||
// Wait for all profiles to complete before proceeding.
|
||||
// Wait for all FetchMatches calls to complete before proceeding.
|
||||
wg.Wait()
|
||||
telemetry.RecordUnitMeasurement(context.Background(), mIterations)
|
||||
logger.Infof("FetchedMatches:%v, AssignedTickets:%v, DeletedTickets:%v in time %v", atomic.LoadUint64(&matchCount), atomic.LoadUint64(&assigned), atomic.LoadUint64(&deleted), time.Since(startTime))
|
||||
}
|
||||
}
|
||||
|
||||
func runFetchMatches(be pb.BackendServiceClient, p *pb.MatchProfile, matchesToAssign chan<- *pb.Match, matchesToAcknowledge chan<- *pb.Match) {
|
||||
ctx, span := trace.StartSpan(context.Background(), "scale.backend/FetchMatches")
|
||||
defer span.End()
|
||||
|
||||
func fetch(be pb.BackendClient, p *pb.MatchProfile, matches chan *pb.Match) {
|
||||
req := &pb.FetchMatchesRequest{
|
||||
Config: &pb.FunctionConfig{
|
||||
Host: "open-match-function",
|
||||
Host: "om-function",
|
||||
Port: 50502,
|
||||
Type: pb.FunctionConfig_GRPC,
|
||||
},
|
||||
Profile: p,
|
||||
Profiles: []*pb.MatchProfile{p},
|
||||
}
|
||||
|
||||
telemetry.RecordUnitMeasurement(ctx, mFetchMatchCalls)
|
||||
stream, err := be.FetchMatches(ctx, req)
|
||||
stream, err := be.FetchMatches(context.Background(), req)
|
||||
if err != nil {
|
||||
telemetry.RecordUnitMeasurement(ctx, mFetchMatchErrors)
|
||||
logger.WithError(err).Error("failed to get available stream client")
|
||||
logger.Errorf("FetchMatches failed, got %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
// Pull the Match
|
||||
resp, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
telemetry.RecordUnitMeasurement(ctx, mFetchMatchSuccesses)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
telemetry.RecordUnitMeasurement(ctx, mFetchMatchErrors)
|
||||
logger.WithError(err).Error("failed to get matches from stream client")
|
||||
logger.Errorf("FetchMatches failed, got %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
telemetry.RecordNUnitMeasurement(ctx, mSumTicketsReturned, int64(len(resp.GetMatch().Tickets)))
|
||||
telemetry.RecordUnitMeasurement(ctx, mMatchesReturned)
|
||||
|
||||
if activeScenario.BackendAssignsTickets {
|
||||
matchesToAssign <- resp.GetMatch()
|
||||
}
|
||||
|
||||
if activeScenario.BackendAcknowledgesBackfills {
|
||||
matchesToAcknowledge <- resp.GetMatch()
|
||||
}
|
||||
matches <- resp.GetMatch()
|
||||
atomic.AddUint64(&matchCount, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func runDeleteBackfills(fe pb.FrontendServiceClient, backfillsToDelete <-chan *pb.Backfill) {
|
||||
for b := range backfillsToDelete {
|
||||
if !activeScenario.BackfillDeleteCond(b) {
|
||||
continue
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
_, err := fe.DeleteBackfill(ctx, &pb.DeleteBackfillRequest{BackfillId: b.Id})
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("failed to delete backfill: %s", b.Id)
|
||||
telemetry.RecordUnitMeasurement(ctx, mBackfillDeletesFailed)
|
||||
} else {
|
||||
telemetry.RecordUnitMeasurement(ctx, mBackfillsDeleted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runAcknowledgeBackfills(fe pb.FrontendServiceClient, matchesToAcknowledge <-chan *pb.Match, backfillsToDelete chan<- *pb.Backfill) {
|
||||
for m := range matchesToAcknowledge {
|
||||
backfillId := m.Backfill.GetId()
|
||||
if backfillId == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
err := acknowledgeBackfill(fe, backfillId)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("failed to acknowledge backfill: %s", backfillId)
|
||||
continue
|
||||
}
|
||||
|
||||
if activeScenario.BackendDeletesBackfills {
|
||||
backfillsToDelete <- m.Backfill
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func acknowledgeBackfill(fe pb.FrontendServiceClient, backfillId string) error {
|
||||
ctx, span := trace.StartSpan(context.Background(), "scale.frontend/AcknowledgeBackfill")
|
||||
defer span.End()
|
||||
|
||||
_, err := fe.AcknowledgeBackfill(ctx, &pb.AcknowledgeBackfillRequest{
|
||||
BackfillId: backfillId,
|
||||
Assignment: &pb.Assignment{
|
||||
Connection: fmt.Sprintf("%d.%d.%d.%d:2222", rand.Intn(256), rand.Intn(256), rand.Intn(256), rand.Intn(256)),
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func runAssignments(be pb.BackendServiceClient, matchesToAssign <-chan *pb.Match) {
|
||||
ctx := context.Background()
|
||||
|
||||
for m := range matchesToAssign {
|
||||
// doAssign continuously assigns matches that were queued in the matches channel
|
||||
// by doFetch and after successful assignment, queues all the tickets to deleteIds
|
||||
// channel for deletion by doDelete.
|
||||
func doAssign(be pb.BackendClient, matches chan *pb.Match, deleteIds chan string) {
|
||||
for match := range matches {
|
||||
ids := []string{}
|
||||
for _, t := range m.Tickets {
|
||||
ids = append(ids, t.GetId())
|
||||
for _, t := range match.Tickets {
|
||||
ids = append(ids, t.Id)
|
||||
}
|
||||
|
||||
_, err := be.AssignTickets(context.Background(), &pb.AssignTicketsRequest{
|
||||
Assignments: []*pb.AssignmentGroup{
|
||||
{
|
||||
TicketIds: ids,
|
||||
Assignment: &pb.Assignment{
|
||||
Connection: fmt.Sprintf("%d.%d.%d.%d:2222", rand.Intn(256), rand.Intn(256), rand.Intn(256), rand.Intn(256)),
|
||||
},
|
||||
},
|
||||
req := &pb.AssignTicketsRequest{
|
||||
TicketIds: ids,
|
||||
Assignment: &pb.Assignment{
|
||||
Connection: fmt.Sprintf("%d.%d.%d.%d:2222", rand.Intn(256), rand.Intn(256), rand.Intn(256), rand.Intn(256)),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
telemetry.RecordUnitMeasurement(ctx, mMatchAssignsFailed)
|
||||
logger.WithError(err).Error("failed to assign tickets")
|
||||
}
|
||||
|
||||
if _, err := be.AssignTickets(context.Background(), req); err != nil {
|
||||
logger.Errorf("AssignTickets failed, got %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
telemetry.RecordUnitMeasurement(ctx, mMatchesAssigned)
|
||||
atomic.AddUint64(&assigned, uint64(len(ids)))
|
||||
for _, id := range ids {
|
||||
deleteIds <- id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// doDelete deletes all the tickets whose ids get added to the deleteIds channel.
|
||||
func doDelete(fe pb.FrontendClient, deleteIds chan string) {
|
||||
for id := range deleteIds {
|
||||
req := &pb.DeleteTicketRequest{
|
||||
TicketId: id,
|
||||
}
|
||||
|
||||
if _, err := fe.DeleteTicket(context.Background(), req); err != nil {
|
||||
logger.Errorf("DeleteTicket failed for ticket %v, got %v", id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
atomic.AddUint64(&deleted, 1)
|
||||
}
|
||||
}
|
||||
|
@ -16,18 +16,15 @@ package frontend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/trace"
|
||||
"open-match.dev/open-match/examples/scale/scenarios"
|
||||
"open-match.dev/open-match/internal/appmain"
|
||||
"open-match.dev/open-match/examples/scale/tickets"
|
||||
"open-match.dev/open-match/internal/config"
|
||||
"open-match.dev/open-match/internal/logging"
|
||||
"open-match.dev/open-match/internal/rpc"
|
||||
"open-match.dev/open-match/internal/telemetry"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
@ -36,206 +33,61 @@ var (
|
||||
"app": "openmatch",
|
||||
"component": "scale.frontend",
|
||||
})
|
||||
activeScenario = scenarios.ActiveScenario
|
||||
|
||||
mTicketsCreated = telemetry.Counter("scale_frontend_tickets_created", "tickets created")
|
||||
mTicketCreationsFailed = telemetry.Counter("scale_frontend_ticket_creations_failed", "tickets created")
|
||||
mRunnersWaiting = concurrentGauge(telemetry.Gauge("scale_frontend_runners_waiting", "runners waiting"))
|
||||
mRunnersCreating = concurrentGauge(telemetry.Gauge("scale_frontend_runners_creating", "runners creating"))
|
||||
mTicketsDeleted = telemetry.Counter("scale_frontend_tickets_deleted", "tickets deleted")
|
||||
mTicketDeletesFailed = telemetry.Counter("scale_frontend_ticket_deletes_failed", "ticket deletes failed")
|
||||
mBackfillsCreated = telemetry.Counter("scale_frontend_backfills_created", "backfills_created")
|
||||
mBackfillCreationsFailed = telemetry.Counter("scale_frontend_backfill_creations_failed", "backfill creations failed")
|
||||
mTicketsTimeToAssignment = telemetry.HistogramWithBounds("scale_frontend_tickets_time_to_assignment", "tickets time to assignment", stats.UnitMilliseconds, []float64{0.01, 0.05, 0.1, 0.3, 0.6, 0.8, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000})
|
||||
)
|
||||
|
||||
type ticketToWatch struct {
|
||||
id string
|
||||
createdAt time.Time
|
||||
}
|
||||
|
||||
// Run triggers execution of the scale frontend component that creates
|
||||
// tickets at scale in Open Match.
|
||||
func BindService(p *appmain.Params, b *appmain.Bindings) error {
|
||||
go run(p.Config())
|
||||
func Run() {
|
||||
cfg, err := config.Read()
|
||||
if err != nil {
|
||||
logger.WithFields(logrus.Fields{
|
||||
"error": err.Error(),
|
||||
}).Fatal("cannot read configuration.")
|
||||
}
|
||||
|
||||
return nil
|
||||
logging.ConfigureLogging(cfg)
|
||||
doCreate(cfg)
|
||||
}
|
||||
|
||||
func run(cfg config.View) {
|
||||
func doCreate(cfg config.View) {
|
||||
concurrent := cfg.GetInt("testConfig.concurrent-creates")
|
||||
conn, err := rpc.GRPCClientFromConfig(cfg, "api.frontend")
|
||||
if err != nil {
|
||||
logger.WithFields(logrus.Fields{
|
||||
"error": err.Error(),
|
||||
}).Fatal("failed to get Frontend connection")
|
||||
}
|
||||
fe := pb.NewFrontendServiceClient(conn)
|
||||
|
||||
if activeScenario.FrontendCreatesBackfillsOnStart {
|
||||
createBackfills(fe, activeScenario.FrontendTotalBackfillsToCreate)
|
||||
}
|
||||
defer conn.Close()
|
||||
fe := pb.NewFrontendClient(conn)
|
||||
|
||||
ticketQPS := int(activeScenario.FrontendTicketCreatedQPS)
|
||||
ticketTotal := activeScenario.FrontendTotalTicketsToCreate
|
||||
totalCreated := 0
|
||||
var created uint64
|
||||
var failed uint64
|
||||
start := time.Now()
|
||||
for {
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i <= concurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
req := &pb.CreateTicketRequest{
|
||||
Ticket: tickets.Ticket(cfg),
|
||||
}
|
||||
|
||||
for range time.Tick(time.Second) {
|
||||
for i := 0; i < ticketQPS; i++ {
|
||||
if ticketTotal == -1 || totalCreated < ticketTotal {
|
||||
go runner(fe)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := fe.CreateTicket(context.Background(), req); err != nil {
|
||||
logger.WithFields(logrus.Fields{
|
||||
"error": err.Error(),
|
||||
}).Error("failed to create a ticket.")
|
||||
atomic.AddUint64(&failed, 1)
|
||||
return
|
||||
}
|
||||
|
||||
func runner(fe pb.FrontendServiceClient) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
g := stateGauge{}
|
||||
defer g.stop()
|
||||
|
||||
g.start(mRunnersWaiting)
|
||||
// A random sleep at the start of the worker evens calls out over the second
|
||||
// period, and makes timing between ticket creation calls a more realistic
|
||||
// poisson distribution.
|
||||
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
||||
|
||||
g.start(mRunnersCreating)
|
||||
createdAt := time.Now()
|
||||
id, err := createTicket(ctx, fe)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("failed to create a ticket")
|
||||
return
|
||||
}
|
||||
|
||||
err = watchAssignments(ctx, fe, ticketToWatch{id: id, createdAt: createdAt})
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("failed to get ticket assignment: %s", id)
|
||||
} else {
|
||||
ms := time.Since(createdAt).Nanoseconds() / 1e6
|
||||
stats.Record(ctx, mTicketsTimeToAssignment.M(ms))
|
||||
}
|
||||
|
||||
if activeScenario.FrontendDeletesTickets {
|
||||
err = deleteTicket(ctx, fe, id)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("failed to delete ticket: %s", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createTicket(ctx context.Context, fe pb.FrontendServiceClient) (string, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "scale.frontend/CreateTicket")
|
||||
defer span.End()
|
||||
|
||||
req := &pb.CreateTicketRequest{
|
||||
Ticket: activeScenario.Ticket(),
|
||||
}
|
||||
|
||||
resp, err := fe.CreateTicket(ctx, req)
|
||||
if err != nil {
|
||||
telemetry.RecordUnitMeasurement(ctx, mTicketCreationsFailed)
|
||||
return "", err
|
||||
}
|
||||
|
||||
telemetry.RecordUnitMeasurement(ctx, mTicketsCreated)
|
||||
return resp.Id, nil
|
||||
}
|
||||
|
||||
func watchAssignments(ctx context.Context, fe pb.FrontendServiceClient, ticket ticketToWatch) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
stream, err := fe.WatchAssignments(ctx, &pb.WatchAssignmentsRequest{TicketId: ticket.id})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var a *pb.Assignment
|
||||
for a.GetConnection() == "" {
|
||||
resp, err := stream.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
atomic.AddUint64(&created, 1)
|
||||
}(&wg)
|
||||
}
|
||||
|
||||
a = resp.Assignment
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createBackfills(fe pb.FrontendServiceClient, numBackfillsToCreate int) error {
|
||||
for i := 0; i < numBackfillsToCreate; i++ {
|
||||
err := createBackfill(fe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createBackfill(fe pb.FrontendServiceClient) error {
|
||||
ctx, span := trace.StartSpan(context.Background(), "scale.frontend/CreateBackfill")
|
||||
defer span.End()
|
||||
|
||||
req := pb.CreateBackfillRequest{
|
||||
Backfill: activeScenario.Backfill(),
|
||||
}
|
||||
|
||||
_, err := fe.CreateBackfill(ctx, &req)
|
||||
if err != nil {
|
||||
telemetry.RecordUnitMeasurement(ctx, mBackfillCreationsFailed)
|
||||
logger.WithError(err).Error("failed to create backfill")
|
||||
return err
|
||||
}
|
||||
|
||||
telemetry.RecordUnitMeasurement(ctx, mBackfillsCreated)
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteTicket(ctx context.Context, fe pb.FrontendServiceClient, ticketId string) error {
|
||||
_, err := fe.DeleteTicket(ctx, &pb.DeleteTicketRequest{TicketId: ticketId})
|
||||
if err != nil {
|
||||
telemetry.RecordUnitMeasurement(ctx, mTicketDeletesFailed)
|
||||
} else {
|
||||
telemetry.RecordUnitMeasurement(ctx, mTicketsDeleted)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Allows concurrent moficiation of a gauge value by modifying the concurrent
|
||||
// value with a delta.
|
||||
func concurrentGauge(s *stats.Int64Measure) func(delta int64) {
|
||||
m := sync.Mutex{}
|
||||
v := int64(0)
|
||||
return func(delta int64) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
v += delta
|
||||
telemetry.SetGauge(context.Background(), s, v)
|
||||
}
|
||||
}
|
||||
|
||||
// stateGauge will have a single value be applied to one gauge at a time.
|
||||
type stateGauge struct {
|
||||
f func(int64)
|
||||
}
|
||||
|
||||
// start begins a stage measured in a gauge, stopping any previously started
|
||||
// stage.
|
||||
func (g *stateGauge) start(f func(int64)) {
|
||||
g.stop()
|
||||
g.f = f
|
||||
f(1)
|
||||
}
|
||||
|
||||
// stop finishes the current stage by decrementing the gauge.
|
||||
func (g *stateGauge) stop() {
|
||||
if g.f != nil {
|
||||
g.f(-1)
|
||||
g.f = nil
|
||||
// Wait for all concurrent creates to complete.
|
||||
wg.Wait()
|
||||
logger.Infof("%v tickets created, %v failed in %v", created, failed, time.Since(start))
|
||||
}
|
||||
}
|
||||
|
@ -1,69 +0,0 @@
|
||||
// Copyright 2019 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 mmf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
|
||||
utilTesting "open-match.dev/open-match/internal/util/testing"
|
||||
|
||||
"open-match.dev/open-match/examples/scale/scenarios"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = logrus.WithFields(logrus.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "scale.mmf",
|
||||
})
|
||||
)
|
||||
|
||||
// Run triggers execution of a MMF.
|
||||
func Run() {
|
||||
activeScenario := scenarios.ActiveScenario
|
||||
|
||||
conn, err := grpc.Dial("open-match-query.open-match.svc.cluster.local:50503", utilTesting.NewGRPCDialOptions(logger)...)
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to connect to Open Match, got %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
server := grpc.NewServer(utilTesting.NewGRPCServerOptions(logger)...)
|
||||
pb.RegisterMatchFunctionServer(server, activeScenario.MMF)
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", 50502))
|
||||
if err != nil {
|
||||
logger.WithFields(logrus.Fields{
|
||||
"error": err.Error(),
|
||||
"port": 50502,
|
||||
}).Fatal("net.Listen() error")
|
||||
}
|
||||
|
||||
logger.WithFields(logrus.Fields{
|
||||
"port": 50502,
|
||||
}).Info("TCP net listener initialized")
|
||||
|
||||
logger.Info("Serving gRPC endpoint")
|
||||
err = server.Serve(ln)
|
||||
if err != nil {
|
||||
logger.WithFields(logrus.Fields{
|
||||
"error": err.Error(),
|
||||
}).Fatal("gRPC serve() error")
|
||||
}
|
||||
}
|
46
examples/scale/profiles/greedy.go
Normal file
46
examples/scale/profiles/greedy.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2019 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 profiles
|
||||
|
||||
import (
|
||||
"open-match.dev/open-match/internal/config"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
// greedyProfiles generates a single profile that has only one Pool that has a single
|
||||
// filter which covers the entire range of player ranks, thereby pulling in the entire
|
||||
// player population during each profile execution.
|
||||
func greedyProfiles(cfg config.View) []*pb.MatchProfile {
|
||||
return []*pb.MatchProfile{
|
||||
{
|
||||
Name: "greedy",
|
||||
Pools: []*pb.Pool{
|
||||
{
|
||||
Name: "all",
|
||||
FloatRangeFilters: []*pb.FloatRangeFilter{
|
||||
{
|
||||
Attribute: "mmr.rating",
|
||||
Min: float64(cfg.GetInt("testConfig.minRating")),
|
||||
Max: float64(cfg.GetInt("testConfig.maxRating")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Rosters: []*pb.Roster{
|
||||
makeRosterSlots("all", cfg.GetInt("testConfig.ticketsPerMatch")),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
80
examples/scale/profiles/multifilter.go
Normal file
80
examples/scale/profiles/multifilter.go
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2019 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 profiles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"open-match.dev/open-match/internal/config"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
// multifilterProfiles generates a multiple profiles, each containing a single Pool
|
||||
// that specifies multiple filters to pick a partitioned player population. Note
|
||||
// that across all the profiles returned, the entire population is covered and given
|
||||
// the overlapping nature of filters, multiple profiles returned by this method may
|
||||
// match to the same set of players.
|
||||
func multifilterProfiles(cfg config.View) []*pb.MatchProfile {
|
||||
regions := cfg.GetStringSlice("testConfig.regions")
|
||||
ratingFilters := makeRangeFilters(&rangeConfig{
|
||||
name: "Rating",
|
||||
min: cfg.GetInt("testConfig.minRating"),
|
||||
max: cfg.GetInt("testConfig.maxRating"),
|
||||
rangeSize: cfg.GetInt("testConfig.multifilter.rangeSize"),
|
||||
rangeOverlap: cfg.GetInt("testConfig.multifilter.rangeOverlap"),
|
||||
})
|
||||
|
||||
latencyFilters := makeRangeFilters(&rangeConfig{
|
||||
name: "Latency",
|
||||
min: 0,
|
||||
max: 100,
|
||||
rangeSize: 70,
|
||||
rangeOverlap: 0,
|
||||
})
|
||||
|
||||
var profiles []*pb.MatchProfile
|
||||
for _, region := range regions {
|
||||
for _, latency := range latencyFilters {
|
||||
for _, rating := range ratingFilters {
|
||||
poolName := fmt.Sprintf("%s_%s_%s", region, rating.name, latency.name)
|
||||
p := &pb.Pool{
|
||||
Name: poolName,
|
||||
FloatRangeFilters: []*pb.FloatRangeFilter{
|
||||
{
|
||||
Attribute: "mmr.rating",
|
||||
Min: float64(rating.min),
|
||||
Max: float64(rating.max),
|
||||
},
|
||||
{
|
||||
Attribute: region,
|
||||
Min: float64(latency.min),
|
||||
Max: float64(latency.max),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
prof := &pb.MatchProfile{
|
||||
Name: fmt.Sprintf("Profile_%s", poolName),
|
||||
Pools: []*pb.Pool{p},
|
||||
Rosters: []*pb.Roster{makeRosterSlots(p.GetName(), cfg.GetInt("testConfig.ticketsPerMatch"))},
|
||||
}
|
||||
|
||||
profiles = append(profiles, prof)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return profiles
|
||||
}
|
93
examples/scale/profiles/multipool.go
Normal file
93
examples/scale/profiles/multipool.go
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright 2019 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 profiles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"open-match.dev/open-match/internal/config"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
// multipoolProfiles generates a multiple profiles, each containing a multiple player
|
||||
// Pools specifying multiple filters each. The profile also requests a roster of a
|
||||
// configured number of players per pool to be placed in a match.
|
||||
func multipoolProfiles(cfg config.View) []*pb.MatchProfile {
|
||||
characters := cfg.GetStringSlice("testConfig.characters")
|
||||
regions := cfg.GetStringSlice("testConfig.regions")
|
||||
ratingFilters := makeRangeFilters(&rangeConfig{
|
||||
name: "Rating",
|
||||
min: cfg.GetInt("testConfig.minRating"),
|
||||
max: cfg.GetInt("testConfig.maxRating"),
|
||||
rangeSize: cfg.GetInt("testConfig.multipool.rangeSize"),
|
||||
rangeOverlap: cfg.GetInt("testConfig.multipool.rangeOverlap"),
|
||||
})
|
||||
|
||||
latencyFilters := makeRangeFilters(&rangeConfig{
|
||||
name: "Latency",
|
||||
min: 0,
|
||||
max: 100,
|
||||
rangeSize: 70,
|
||||
rangeOverlap: 0,
|
||||
})
|
||||
|
||||
var profiles []*pb.MatchProfile
|
||||
for _, region := range regions {
|
||||
for _, latency := range latencyFilters {
|
||||
for _, rating := range ratingFilters {
|
||||
var pools []*pb.Pool
|
||||
var rosters []*pb.Roster
|
||||
for _, character := range characters {
|
||||
poolName := fmt.Sprintf("%s_%s_%s_%s", region, rating.name, latency.name, character)
|
||||
p := &pb.Pool{
|
||||
Name: poolName,
|
||||
FloatRangeFilters: []*pb.FloatRangeFilter{
|
||||
// TODO: Use StringEqualsFilter for the character attribute.
|
||||
{
|
||||
Attribute: character,
|
||||
Min: 0,
|
||||
Max: math.MaxFloat64,
|
||||
},
|
||||
{
|
||||
Attribute: "mmr.rating",
|
||||
Min: float64(rating.min),
|
||||
Max: float64(rating.max),
|
||||
},
|
||||
{
|
||||
Attribute: region,
|
||||
Min: float64(latency.min),
|
||||
Max: float64(latency.max),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rosters = append(rosters, makeRosterSlots(poolName, cfg.GetInt("testConfig.multipool.characterCount")))
|
||||
pools = append(pools, p)
|
||||
}
|
||||
|
||||
prof := &pb.MatchProfile{
|
||||
Name: fmt.Sprintf("Profile_%s", fmt.Sprintf("%s_%s_%s", region, rating.name, latency.name)),
|
||||
Pools: pools,
|
||||
Rosters: rosters,
|
||||
}
|
||||
|
||||
profiles = append(profiles, prof)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return profiles
|
||||
}
|
53
examples/scale/profiles/profiles.go
Normal file
53
examples/scale/profiles/profiles.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2019 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 profiles
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"open-match.dev/open-match/internal/config"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = logrus.WithFields(logrus.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "scale.profiles",
|
||||
})
|
||||
|
||||
// Greedy config type is used to pick profiles that pull in all players.
|
||||
Greedy = "greedy"
|
||||
// MultiFilter config type is used to pick profiles that have a Pool that uses multiple filters.
|
||||
MultiFilter = "multifilter"
|
||||
// MultiPool config type is used to pick profiles that have multiple Pools with multiple filters each.
|
||||
MultiPool = "multipool"
|
||||
// emptyRosterSpot is the string that represents an empty slot on a Roster.
|
||||
emptyRosterSpot = "EMPTY_ROSTER_SPOT"
|
||||
)
|
||||
|
||||
// Generate generates test profiles for scale demo
|
||||
func Generate(cfg config.View) []*pb.MatchProfile {
|
||||
profile := cfg.GetString("testConfig.profile")
|
||||
switch profile {
|
||||
case Greedy:
|
||||
return greedyProfiles(cfg)
|
||||
case MultiFilter:
|
||||
return multifilterProfiles(cfg)
|
||||
case MultiPool:
|
||||
return multipoolProfiles(cfg)
|
||||
}
|
||||
|
||||
logger.Warningf("Unexpected profile name %s, not returning any profiles", profile)
|
||||
return nil
|
||||
}
|
72
examples/scale/profiles/util.go
Normal file
72
examples/scale/profiles/util.go
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright 2019 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 profiles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
type rangeFilter struct {
|
||||
name string
|
||||
min int
|
||||
max int
|
||||
}
|
||||
|
||||
type rangeConfig struct {
|
||||
name string
|
||||
min int
|
||||
max int
|
||||
rangeSize int
|
||||
rangeOverlap int
|
||||
}
|
||||
|
||||
// makeRosterSlots generates a roster with the specified name and with the
|
||||
// specified number of empty roster slots.
|
||||
func makeRosterSlots(name string, count int) *pb.Roster {
|
||||
roster := &pb.Roster{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
for i := 0; i <= count; i++ {
|
||||
roster.TicketIds = append(roster.TicketIds, emptyRosterSpot)
|
||||
}
|
||||
|
||||
return roster
|
||||
}
|
||||
|
||||
// makeRangeFilters generates multiple filters over a given range based on
|
||||
// the size of the range and the overlap specified for the filters.
|
||||
func makeRangeFilters(config *rangeConfig) []*rangeFilter {
|
||||
var filters []*rangeFilter
|
||||
r := config.min
|
||||
for r <= config.max {
|
||||
max := r + config.rangeSize
|
||||
if max > config.max {
|
||||
r = config.max
|
||||
}
|
||||
|
||||
filters = append(filters, &rangeFilter{
|
||||
name: fmt.Sprintf("%s_%dto%d", config.name, r, max),
|
||||
min: r,
|
||||
max: max,
|
||||
})
|
||||
|
||||
r = r + 1 + (config.rangeSize - config.rangeOverlap)
|
||||
}
|
||||
|
||||
return filters
|
||||
}
|
@ -1,271 +0,0 @@
|
||||
// Copyright 2020 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 backfill
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/golang/protobuf/ptypes/any"
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
const (
|
||||
poolName = "all"
|
||||
openSlotsKey = "open-slots"
|
||||
)
|
||||
|
||||
func Scenario() *BackfillScenario {
|
||||
ticketsPerMatch := 4
|
||||
return &BackfillScenario{
|
||||
TicketsPerMatch: ticketsPerMatch,
|
||||
MaxTicketsPerNotFullMatch: 3,
|
||||
BackfillDeleteCond: func(b *pb.Backfill) bool {
|
||||
openSlots := getOpenSlots(b, ticketsPerMatch)
|
||||
return openSlots <= 0
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type BackfillScenario struct {
|
||||
TicketsPerMatch int
|
||||
MaxTicketsPerNotFullMatch int
|
||||
BackfillDeleteCond func(*pb.Backfill) bool
|
||||
}
|
||||
|
||||
func (s *BackfillScenario) Profiles() []*pb.MatchProfile {
|
||||
return []*pb.MatchProfile{
|
||||
{
|
||||
Name: "entirePool",
|
||||
Pools: []*pb.Pool{
|
||||
{
|
||||
Name: poolName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BackfillScenario) Ticket() *pb.Ticket {
|
||||
return &pb.Ticket{}
|
||||
}
|
||||
|
||||
func (s *BackfillScenario) Backfill() *pb.Backfill {
|
||||
return &pb.Backfill{}
|
||||
}
|
||||
|
||||
func (s *BackfillScenario) MatchFunction(p *pb.MatchProfile, poolBackfills map[string][]*pb.Backfill, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error) {
|
||||
return statefullMMF(p, poolBackfills, poolTickets, s.TicketsPerMatch, s.MaxTicketsPerNotFullMatch)
|
||||
}
|
||||
|
||||
// statefullMMF is a MMF implementation which is used in scenario when we want MMF to create not full match and fill it later.
|
||||
// 1. The first FetchMatches is called
|
||||
// 2. MMF grabs maxTicketsPerNotFullMatch tickets and makes a match and new backfill for it
|
||||
// 3. MMF sets backfill's open slots to ticketsPerMatch - maxTicketsPerNotFullMatch
|
||||
// 4. MMF returns the match as a result
|
||||
// 5. The second FetchMatches is called
|
||||
// 6. MMF gets previously created backfill
|
||||
// 7. MMF gets backfill's open slots value
|
||||
// 8. MMF grabs openSlots tickets and makes a match with previously created backfill
|
||||
// 9. MMF sets backfill's open slots to 0
|
||||
// 10. MMF returns the match as a result
|
||||
func statefullMMF(p *pb.MatchProfile, poolBackfills map[string][]*pb.Backfill, poolTickets map[string][]*pb.Ticket, ticketsPerMatch int, maxTicketsPerNotFullMatch int) ([]*pb.Match, error) {
|
||||
var matches []*pb.Match
|
||||
|
||||
for pool, backfills := range poolBackfills {
|
||||
tickets, ok := poolTickets[pool]
|
||||
|
||||
if !ok || len(tickets) == 0 {
|
||||
// no tickets in pool
|
||||
continue
|
||||
}
|
||||
|
||||
// process backfills first
|
||||
for _, b := range backfills {
|
||||
l := len(tickets)
|
||||
if l == 0 {
|
||||
// no tickets left
|
||||
break
|
||||
}
|
||||
|
||||
openSlots := getOpenSlots(b, ticketsPerMatch)
|
||||
if openSlots <= 0 {
|
||||
// no free open slots
|
||||
continue
|
||||
}
|
||||
|
||||
if l > openSlots {
|
||||
l = openSlots
|
||||
}
|
||||
|
||||
setOpenSlots(b, openSlots-l)
|
||||
matches = append(matches, &pb.Match{
|
||||
MatchId: fmt.Sprintf("profile-%v-time-%v-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00"), len(matches)),
|
||||
Tickets: tickets[0:l],
|
||||
MatchProfile: p.GetName(),
|
||||
MatchFunction: "backfill",
|
||||
Backfill: b,
|
||||
})
|
||||
tickets = tickets[l:]
|
||||
}
|
||||
|
||||
// create not full matches with backfill
|
||||
for {
|
||||
l := len(tickets)
|
||||
if l == 0 {
|
||||
// no tickets left
|
||||
break
|
||||
}
|
||||
|
||||
if l > maxTicketsPerNotFullMatch {
|
||||
l = maxTicketsPerNotFullMatch
|
||||
}
|
||||
b := pb.Backfill{}
|
||||
setOpenSlots(&b, ticketsPerMatch-l)
|
||||
matches = append(matches, &pb.Match{
|
||||
MatchId: fmt.Sprintf("profile-%v-time-%v-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00"), len(matches)),
|
||||
Tickets: tickets[0:l],
|
||||
MatchProfile: p.GetName(),
|
||||
MatchFunction: "backfill",
|
||||
Backfill: &b,
|
||||
AllocateGameserver: true,
|
||||
})
|
||||
tickets = tickets[l:]
|
||||
}
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
func getOpenSlots(b *pb.Backfill, defaultVal int) int {
|
||||
if b.Extensions == nil {
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
any, ok := b.Extensions[openSlotsKey]
|
||||
if !ok {
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
var val wrappers.Int32Value
|
||||
err := ptypes.UnmarshalAny(any, &val)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(val.Value)
|
||||
}
|
||||
|
||||
func setOpenSlots(b *pb.Backfill, val int) {
|
||||
if b.Extensions == nil {
|
||||
b.Extensions = make(map[string]*any.Any)
|
||||
}
|
||||
|
||||
any, err := ptypes.MarshalAny(&wrappers.Int32Value{Value: int32(val)})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
b.Extensions[openSlotsKey] = any
|
||||
}
|
||||
|
||||
// statelessMMF is a MMF implementation which is used in scenario when we want MMF to fill backfills created by a Gameserver. It doesn't create
|
||||
// or update any backfill.
|
||||
// 1. FetchMatches is called
|
||||
// 2. MMF gets a backfill
|
||||
// 3. MMF grabs ticketsPerMatch tickets and makes a match with the backfill
|
||||
// 4. MMF returns the match as a result
|
||||
func statelessMMF(p *pb.MatchProfile, poolBackfills map[string][]*pb.Backfill, poolTickets map[string][]*pb.Ticket, ticketsPerMatch int) ([]*pb.Match, error) {
|
||||
var matches []*pb.Match
|
||||
|
||||
for pool, backfills := range poolBackfills {
|
||||
tickets, ok := poolTickets[pool]
|
||||
|
||||
if !ok || len(tickets) == 0 {
|
||||
// no tickets in pool
|
||||
continue
|
||||
}
|
||||
|
||||
for _, b := range backfills {
|
||||
l := len(tickets)
|
||||
if l == 0 {
|
||||
// no tickets left
|
||||
break
|
||||
}
|
||||
|
||||
if l > ticketsPerMatch && ticketsPerMatch > 0 {
|
||||
l = ticketsPerMatch
|
||||
}
|
||||
|
||||
matches = append(matches, &pb.Match{
|
||||
MatchId: fmt.Sprintf("profile-%v-time-%v-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00"), len(matches)),
|
||||
Tickets: tickets[0:l],
|
||||
MatchProfile: p.GetName(),
|
||||
MatchFunction: "backfill",
|
||||
Backfill: b,
|
||||
})
|
||||
tickets = tickets[l:]
|
||||
}
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
func (s *BackfillScenario) Evaluate(stream pb.Evaluator_EvaluateServer) error {
|
||||
tickets := map[string]struct{}{}
|
||||
backfills := map[string]struct{}{}
|
||||
matchIds := []string{}
|
||||
|
||||
outer:
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read evaluator input stream: %w", err)
|
||||
}
|
||||
|
||||
m := req.GetMatch()
|
||||
|
||||
if _, ok := backfills[m.Backfill.Id]; ok {
|
||||
continue outer
|
||||
}
|
||||
|
||||
for _, t := range m.Tickets {
|
||||
if _, ok := tickets[t.Id]; ok {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range m.Tickets {
|
||||
tickets[t.Id] = struct{}{}
|
||||
}
|
||||
|
||||
matchIds = append(matchIds, m.GetMatchId())
|
||||
}
|
||||
|
||||
for _, id := range matchIds {
|
||||
err := stream.Send(&pb.EvaluateResponse{MatchId: id})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sending evaluator output stream: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
// Copyright 2019 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 battleroyal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
const (
|
||||
poolName = "all"
|
||||
regionArg = "region"
|
||||
)
|
||||
|
||||
func battleRoyalRegionName(i int) string {
|
||||
return fmt.Sprintf("region_%d", i)
|
||||
}
|
||||
|
||||
func Scenario() *BattleRoyalScenario {
|
||||
return &BattleRoyalScenario{
|
||||
regions: 20,
|
||||
}
|
||||
}
|
||||
|
||||
type BattleRoyalScenario struct {
|
||||
regions int
|
||||
}
|
||||
|
||||
func (b *BattleRoyalScenario) Profiles() []*pb.MatchProfile {
|
||||
p := []*pb.MatchProfile{}
|
||||
|
||||
for i := 0; i < b.regions; i++ {
|
||||
p = append(p, &pb.MatchProfile{
|
||||
Name: battleRoyalRegionName(i),
|
||||
Pools: []*pb.Pool{
|
||||
{
|
||||
Name: poolName,
|
||||
StringEqualsFilters: []*pb.StringEqualsFilter{
|
||||
{
|
||||
StringArg: regionArg,
|
||||
Value: battleRoyalRegionName(i),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (b *BattleRoyalScenario) Ticket() *pb.Ticket {
|
||||
// Simple way to give an uneven distribution of region population.
|
||||
a := rand.Intn(b.regions) + 1
|
||||
r := rand.Intn(a)
|
||||
|
||||
return &pb.Ticket{
|
||||
SearchFields: &pb.SearchFields{
|
||||
StringArgs: map[string]string{
|
||||
regionArg: battleRoyalRegionName(r),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BattleRoyalScenario) Backfill() *pb.Backfill {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BattleRoyalScenario) MatchFunction(p *pb.MatchProfile, poolBackfills map[string][]*pb.Backfill, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error) {
|
||||
const playersInMatch = 100
|
||||
|
||||
tickets := poolTickets[poolName]
|
||||
var matches []*pb.Match
|
||||
|
||||
for i := 0; i+playersInMatch <= len(tickets); i += playersInMatch {
|
||||
matches = append(matches, &pb.Match{
|
||||
MatchId: fmt.Sprintf("profile-%v-time-%v-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00"), len(matches)),
|
||||
Tickets: tickets[i : i+playersInMatch],
|
||||
MatchProfile: p.GetName(),
|
||||
MatchFunction: "battleRoyal",
|
||||
})
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// fifoEvaluate accepts all matches which don't contain the same ticket as in a
|
||||
// previously accepted match. Essentially first to claim the ticket wins.
|
||||
func (b *BattleRoyalScenario) Evaluate(stream pb.Evaluator_EvaluateServer) error {
|
||||
used := map[string]struct{}{}
|
||||
|
||||
// TODO: once the evaluator client supports sending and receiving at the
|
||||
// same time, don't buffer, just send results immediately.
|
||||
matchIDs := []string{}
|
||||
|
||||
outer:
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading evaluator input stream: %w", err)
|
||||
}
|
||||
|
||||
m := req.GetMatch()
|
||||
|
||||
for _, t := range m.Tickets {
|
||||
if _, ok := used[t.Id]; ok {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range m.Tickets {
|
||||
used[t.Id] = struct{}{}
|
||||
}
|
||||
|
||||
matchIDs = append(matchIDs, m.GetMatchId())
|
||||
}
|
||||
|
||||
for _, mID := range matchIDs {
|
||||
err := stream.Send(&pb.EvaluateResponse{MatchId: mID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error sending evaluator output stream: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
// Copyright 2019 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 firstmatch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
const (
|
||||
poolName = "all"
|
||||
)
|
||||
|
||||
func Scenario() *FirstMatchScenario {
|
||||
return &FirstMatchScenario{}
|
||||
}
|
||||
|
||||
type FirstMatchScenario struct {
|
||||
}
|
||||
|
||||
func (*FirstMatchScenario) Profiles() []*pb.MatchProfile {
|
||||
return []*pb.MatchProfile{
|
||||
{
|
||||
Name: "entirePool",
|
||||
Pools: []*pb.Pool{
|
||||
{
|
||||
Name: poolName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (*FirstMatchScenario) Ticket() *pb.Ticket {
|
||||
return &pb.Ticket{}
|
||||
}
|
||||
|
||||
func (*FirstMatchScenario) Backfill() *pb.Backfill {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*FirstMatchScenario) MatchFunction(p *pb.MatchProfile, poolBackfills map[string][]*pb.Backfill, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error) {
|
||||
tickets := poolTickets[poolName]
|
||||
var matches []*pb.Match
|
||||
|
||||
for i := 0; i+1 < len(tickets); i += 2 {
|
||||
matches = append(matches, &pb.Match{
|
||||
MatchId: fmt.Sprintf("profile-%v-time-%v-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00"), len(matches)),
|
||||
Tickets: []*pb.Ticket{tickets[i], tickets[i+1]},
|
||||
MatchProfile: p.GetName(),
|
||||
MatchFunction: "rangeExpandingMatchFunction",
|
||||
})
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// fifoEvaluate accepts all matches which don't contain the same ticket as in a
|
||||
// previously accepted match. Essentially first to claim the ticket wins.
|
||||
func (*FirstMatchScenario) Evaluate(stream pb.Evaluator_EvaluateServer) error {
|
||||
used := map[string]struct{}{}
|
||||
|
||||
// TODO: once the evaluator client supports sending and receiving at the
|
||||
// same time, don't buffer, just send results immediately.
|
||||
matchIDs := []string{}
|
||||
|
||||
outer:
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading evaluator input stream: %w", err)
|
||||
}
|
||||
|
||||
m := req.GetMatch()
|
||||
|
||||
for _, t := range m.Tickets {
|
||||
if _, ok := used[t.Id]; ok {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range m.Tickets {
|
||||
used[t.Id] = struct{}{}
|
||||
}
|
||||
|
||||
matchIDs = append(matchIDs, m.GetMatchId())
|
||||
}
|
||||
|
||||
for _, mID := range matchIDs {
|
||||
err := stream.Send(&pb.EvaluateResponse{MatchId: mID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error sending evaluator output stream: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
// Copyright 2019 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 scenarios
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
"open-match.dev/open-match/examples/scale/scenarios/backfill"
|
||||
"open-match.dev/open-match/examples/scale/scenarios/firstmatch"
|
||||
"open-match.dev/open-match/internal/util/testing"
|
||||
"open-match.dev/open-match/pkg/matchfunction"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
var (
|
||||
queryServiceAddress = "open-match-query.open-match.svc.cluster.local:50503" // Address of the QueryService Endpoint.
|
||||
|
||||
logger = logrus.WithFields(logrus.Fields{
|
||||
"app": "scale",
|
||||
})
|
||||
)
|
||||
|
||||
// GameScenario defines what tickets look like, and how they should be matched.
|
||||
type GameScenario interface {
|
||||
// Ticket creates a new ticket, with randomized parameters.
|
||||
Ticket() *pb.Ticket
|
||||
|
||||
// Backfill creates a new backfill, with randomized parameters.
|
||||
Backfill() *pb.Backfill
|
||||
|
||||
// Profiles lists all of the profiles that should run.
|
||||
Profiles() []*pb.MatchProfile
|
||||
|
||||
// MatchFunction is the custom logic implementation of the match function.
|
||||
MatchFunction(p *pb.MatchProfile, poolBackfills map[string][]*pb.Backfill, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error)
|
||||
|
||||
// Evaluate is the custom logic implementation of the evaluator.
|
||||
Evaluate(stream pb.Evaluator_EvaluateServer) error
|
||||
}
|
||||
|
||||
// ActiveScenario sets the scenario with preset parameters that we want to use for current Open Match benchmark run.
|
||||
var ActiveScenario = func() *Scenario {
|
||||
var gs GameScenario = firstmatch.Scenario()
|
||||
|
||||
// TODO: Select which scenario to use based on some configuration or choice,
|
||||
// so it's easier to run different scenarios without changing code.
|
||||
//gs = battleroyal.Scenario()
|
||||
//gs = teamshooter.Scenario()
|
||||
s := backfill.Scenario()
|
||||
gs = s
|
||||
|
||||
return &Scenario{
|
||||
FrontendTotalTicketsToCreate: -1,
|
||||
FrontendTicketCreatedQPS: 100,
|
||||
FrontendCreatesBackfillsOnStart: true,
|
||||
FrontendTotalBackfillsToCreate: 1000,
|
||||
FrontendDeletesTickets: true,
|
||||
|
||||
BackendAssignsTickets: false,
|
||||
BackendAcknowledgesBackfills: true,
|
||||
BackendDeletesBackfills: true,
|
||||
|
||||
Ticket: gs.Ticket,
|
||||
Backfill: gs.Backfill,
|
||||
BackfillDeleteCond: s.BackfillDeleteCond,
|
||||
Profiles: gs.Profiles,
|
||||
|
||||
MMF: queryPoolsWrapper(gs.MatchFunction),
|
||||
Evaluator: gs.Evaluate,
|
||||
}
|
||||
}()
|
||||
|
||||
// Scenario defines the controllable fields for Open Match benchmark scenarios
|
||||
type Scenario struct {
|
||||
// TODO: supports the following controllable parameters
|
||||
|
||||
// MatchFunction Configs
|
||||
// MatchOverlapRatio float32
|
||||
// TicketSearchFieldsUnitSize int
|
||||
// TicketSearchFieldsNumber int
|
||||
|
||||
// GameFrontend Configs
|
||||
// TicketExtensionSize int
|
||||
// PendingTicketNumber int
|
||||
// MatchExtensionSize int
|
||||
FrontendTicketCreatedQPS uint32
|
||||
FrontendTotalTicketsToCreate int // TotalTicketsToCreate = -1 let scale-frontend create tickets forever
|
||||
FrontendTotalBackfillsToCreate int
|
||||
FrontendCreatesBackfillsOnStart bool
|
||||
FrontendDeletesTickets bool
|
||||
|
||||
// GameBackend Configs
|
||||
// ProfileNumber int
|
||||
// FilterNumber int
|
||||
BackendAssignsTickets bool
|
||||
BackendAcknowledgesBackfills bool
|
||||
BackendDeletesBackfills bool
|
||||
|
||||
Ticket func() *pb.Ticket
|
||||
Backfill func() *pb.Backfill
|
||||
BackfillDeleteCond func(*pb.Backfill) bool
|
||||
Profiles func() []*pb.MatchProfile
|
||||
|
||||
MMF matchFunction
|
||||
Evaluator evaluatorFunction
|
||||
}
|
||||
|
||||
type matchFunction func(*pb.RunRequest, pb.MatchFunction_RunServer) error
|
||||
type evaluatorFunction func(pb.Evaluator_EvaluateServer) error
|
||||
|
||||
func (mmf matchFunction) Run(req *pb.RunRequest, srv pb.MatchFunction_RunServer) error {
|
||||
return mmf(req, srv)
|
||||
}
|
||||
|
||||
func (eval evaluatorFunction) Evaluate(srv pb.Evaluator_EvaluateServer) error {
|
||||
return eval(srv)
|
||||
}
|
||||
|
||||
func getQueryServiceGRPCClient() pb.QueryServiceClient {
|
||||
conn, err := grpc.Dial(queryServiceAddress, testing.NewGRPCDialOptions(logger)...)
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to connect to Open Match, got %v", err)
|
||||
}
|
||||
return pb.NewQueryServiceClient(conn)
|
||||
}
|
||||
|
||||
func queryPoolsWrapper(mmf func(req *pb.MatchProfile, poolBackfills map[string][]*pb.Backfill, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error)) matchFunction {
|
||||
var q pb.QueryServiceClient
|
||||
var startQ sync.Once
|
||||
|
||||
return func(req *pb.RunRequest, stream pb.MatchFunction_RunServer) error {
|
||||
startQ.Do(func() {
|
||||
q = getQueryServiceGRPCClient()
|
||||
})
|
||||
|
||||
poolTickets, err := matchfunction.QueryPools(stream.Context(), q, req.GetProfile().GetPools())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
poolBackfills, err := matchfunction.QueryBackfillPools(stream.Context(), q, req.GetProfile().GetPools())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proposals, err := mmf(req.GetProfile(), poolBackfills, poolTickets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.WithFields(logrus.Fields{
|
||||
"proposals": proposals,
|
||||
}).Trace("proposals returned by match function")
|
||||
|
||||
for _, proposal := range proposals {
|
||||
if err := stream.Send(&pb.RunResponse{Proposal: proposal}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,334 +0,0 @@
|
||||
// Copyright 2019 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.
|
||||
|
||||
// TeamShooterScenario is a scenario which is designed to emulate the
|
||||
// approximate behavior to open match that a skill based team game would have.
|
||||
// It doesn't try to provide good matchmaking for real players. There are three
|
||||
// arguments used:
|
||||
// mode: The game mode the players wants to play in. mode is a hard partition.
|
||||
// regions: Players may have good latency to one or more regions. A player will
|
||||
// search for matches in all eligible regions.
|
||||
// skill: Players have a random skill based on a normal distribution. Players
|
||||
// will only be matched with other players who have a close skill value. The
|
||||
// match functions have overlapping partitions of the skill brackets.
|
||||
package teamshooter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/golang/protobuf/ptypes/any"
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
)
|
||||
|
||||
const (
|
||||
poolName = "all"
|
||||
skillArg = "skill"
|
||||
modeArg = "mode"
|
||||
)
|
||||
|
||||
// TeamShooterScenario provides the required methods for running a scenario.
|
||||
type TeamShooterScenario struct {
|
||||
// Names of available region tags.
|
||||
regions []string
|
||||
// Maximum regions a player can search in.
|
||||
maxRegions int
|
||||
// Number of tickets which form a match.
|
||||
playersPerGame int
|
||||
// For each pair of consequitive values, the value to split profiles on by
|
||||
// skill.
|
||||
skillBoundaries []float64
|
||||
// Maximum difference between two tickets to consider a match valid.
|
||||
maxSkillDifference float64
|
||||
// List of mode names.
|
||||
modes []string
|
||||
// Returns a random mode, with some weight.
|
||||
randomMode func() string
|
||||
}
|
||||
|
||||
// Scenario creates a new TeamShooterScenario.
|
||||
func Scenario() *TeamShooterScenario {
|
||||
|
||||
modes, randomMode := weightedChoice(map[string]int{
|
||||
"pl": 100, // Payload, very popular.
|
||||
"cp": 25, // Capture point, 1/4 as popular.
|
||||
})
|
||||
|
||||
regions := []string{}
|
||||
for i := 0; i < 2; i++ {
|
||||
regions = append(regions, fmt.Sprintf("region_%d", i))
|
||||
}
|
||||
|
||||
return &TeamShooterScenario{
|
||||
regions: regions,
|
||||
maxRegions: 1,
|
||||
playersPerGame: 12,
|
||||
skillBoundaries: []float64{math.Inf(-1), 0, math.Inf(1)},
|
||||
maxSkillDifference: 0.01,
|
||||
modes: modes,
|
||||
randomMode: randomMode,
|
||||
}
|
||||
}
|
||||
|
||||
// Profiles shards the player base on mode, region, and skill.
|
||||
func (t *TeamShooterScenario) Profiles() []*pb.MatchProfile {
|
||||
p := []*pb.MatchProfile{}
|
||||
|
||||
for _, region := range t.regions {
|
||||
for _, mode := range t.modes {
|
||||
for i := 0; i+1 < len(t.skillBoundaries); i++ {
|
||||
skillMin := t.skillBoundaries[i] - t.maxSkillDifference/2
|
||||
skillMax := t.skillBoundaries[i+1] + t.maxSkillDifference/2
|
||||
p = append(p, &pb.MatchProfile{
|
||||
Name: fmt.Sprintf("%s_%s_%v-%v", region, mode, skillMin, skillMax),
|
||||
Pools: []*pb.Pool{
|
||||
{
|
||||
Name: poolName,
|
||||
DoubleRangeFilters: []*pb.DoubleRangeFilter{
|
||||
{
|
||||
DoubleArg: skillArg,
|
||||
Min: skillMin,
|
||||
Max: skillMax,
|
||||
},
|
||||
},
|
||||
TagPresentFilters: []*pb.TagPresentFilter{
|
||||
{
|
||||
Tag: region,
|
||||
},
|
||||
},
|
||||
StringEqualsFilters: []*pb.StringEqualsFilter{
|
||||
{
|
||||
StringArg: modeArg,
|
||||
Value: mode,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Ticket creates a randomized player.
|
||||
func (t *TeamShooterScenario) Ticket() *pb.Ticket {
|
||||
region := rand.Intn(len(t.regions))
|
||||
numRegions := rand.Intn(t.maxRegions) + 1
|
||||
|
||||
tags := []string{}
|
||||
for i := 0; i < numRegions; i++ {
|
||||
tags = append(tags, t.regions[region])
|
||||
// The Earth is actually a circle.
|
||||
region = (region + 1) % len(t.regions)
|
||||
}
|
||||
|
||||
return &pb.Ticket{
|
||||
SearchFields: &pb.SearchFields{
|
||||
DoubleArgs: map[string]float64{
|
||||
skillArg: clamp(rand.NormFloat64(), -3, 3),
|
||||
},
|
||||
StringArgs: map[string]string{
|
||||
modeArg: t.randomMode(),
|
||||
},
|
||||
Tags: tags,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TeamShooterScenario) Backfill() *pb.Backfill {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MatchFunction puts tickets into matches based on their skill, finding the
|
||||
// required number of tickets for a game within the maximum skill difference.
|
||||
func (t *TeamShooterScenario) MatchFunction(p *pb.MatchProfile, poolBackfills map[string][]*pb.Backfill, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error) {
|
||||
skill := func(t *pb.Ticket) float64 {
|
||||
return t.SearchFields.DoubleArgs[skillArg]
|
||||
}
|
||||
|
||||
tickets := poolTickets[poolName]
|
||||
var matches []*pb.Match
|
||||
|
||||
sort.Slice(tickets, func(i, j int) bool {
|
||||
return skill(tickets[i]) < skill(tickets[j])
|
||||
})
|
||||
|
||||
for i := 0; i+t.playersPerGame <= len(tickets); i++ {
|
||||
mt := tickets[i : i+t.playersPerGame]
|
||||
if skill(mt[len(mt)-1])-skill(mt[0]) < t.maxSkillDifference {
|
||||
avg := float64(0)
|
||||
for _, t := range mt {
|
||||
avg += skill(t)
|
||||
}
|
||||
avg /= float64(len(mt))
|
||||
|
||||
q := float64(0)
|
||||
for _, t := range mt {
|
||||
diff := skill(t) - avg
|
||||
q -= diff * diff
|
||||
}
|
||||
|
||||
m, err := (&matchExt{
|
||||
id: fmt.Sprintf("profile-%v-time-%v-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00"), len(matches)),
|
||||
matchProfile: p.GetName(),
|
||||
matchFunction: "skillmatcher",
|
||||
tickets: mt,
|
||||
quality: q,
|
||||
}).pack()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matches = append(matches, m)
|
||||
}
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// Evaluate returns matches in order of highest quality, skipping any matches
|
||||
// which contain tickets that are already used.
|
||||
func (t *TeamShooterScenario) Evaluate(stream pb.Evaluator_EvaluateServer) error {
|
||||
// Unpacked proposal matches.
|
||||
proposals := []*matchExt{}
|
||||
// Ticket ids which are used in a match.
|
||||
used := map[string]struct{}{}
|
||||
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading evaluator input stream: %w", err)
|
||||
}
|
||||
|
||||
p, err := unpackMatch(req.GetMatch())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proposals = append(proposals, p)
|
||||
}
|
||||
|
||||
// Higher quality is better.
|
||||
sort.Slice(proposals, func(i, j int) bool {
|
||||
return proposals[i].quality > proposals[j].quality
|
||||
})
|
||||
|
||||
outer:
|
||||
for _, p := range proposals {
|
||||
for _, t := range p.tickets {
|
||||
if _, ok := used[t.Id]; ok {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range p.tickets {
|
||||
used[t.Id] = struct{}{}
|
||||
}
|
||||
|
||||
err := stream.Send(&pb.EvaluateResponse{MatchId: p.id})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error sending evaluator output stream: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchExt presents the match and extension data in a native form, and allows
|
||||
// easy conversion to and from proto format.
|
||||
type matchExt struct {
|
||||
id string
|
||||
tickets []*pb.Ticket
|
||||
quality float64
|
||||
matchProfile string
|
||||
matchFunction string
|
||||
}
|
||||
|
||||
func unpackMatch(m *pb.Match) (*matchExt, error) {
|
||||
v := &wrappers.DoubleValue{}
|
||||
|
||||
err := ptypes.UnmarshalAny(m.Extensions["quality"], v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error unpacking match quality: %w", err)
|
||||
}
|
||||
|
||||
return &matchExt{
|
||||
id: m.MatchId,
|
||||
tickets: m.Tickets,
|
||||
quality: v.Value,
|
||||
matchProfile: m.MatchProfile,
|
||||
matchFunction: m.MatchFunction,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *matchExt) pack() (*pb.Match, error) {
|
||||
v := &wrappers.DoubleValue{Value: m.quality}
|
||||
|
||||
a, err := ptypes.MarshalAny(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error packing match quality: %w", err)
|
||||
}
|
||||
|
||||
return &pb.Match{
|
||||
MatchId: m.id,
|
||||
Tickets: m.tickets,
|
||||
MatchProfile: m.matchProfile,
|
||||
MatchFunction: m.matchFunction,
|
||||
Extensions: map[string]*any.Any{
|
||||
"quality": a,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func clamp(v float64, min float64, max float64) float64 {
|
||||
if v < min {
|
||||
return min
|
||||
}
|
||||
if v > max {
|
||||
return max
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// weightedChoice takes a map of values, and their relative probability. It
|
||||
// returns a list of the values, along with a function which will return random
|
||||
// choices from the values with the weighted probability.
|
||||
func weightedChoice(m map[string]int) ([]string, func() string) {
|
||||
s := make([]string, 0, len(m))
|
||||
total := 0
|
||||
for k, v := range m {
|
||||
s = append(s, k)
|
||||
total += v
|
||||
}
|
||||
|
||||
return s, func() string {
|
||||
remainder := rand.Intn(total)
|
||||
for k, v := range m {
|
||||
remainder -= v
|
||||
if remainder < 0 {
|
||||
return k
|
||||
}
|
||||
}
|
||||
panic("weightedChoice is broken.")
|
||||
}
|
||||
}
|
81
examples/scale/tickets/tickets.go
Normal file
81
examples/scale/tickets/tickets.go
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright 2019 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 tickets
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"open-match.dev/open-match/internal/config"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"open-match.dev/open-match/pkg/pb"
|
||||
"open-match.dev/open-match/pkg/structs"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = logrus.WithFields(logrus.Fields{
|
||||
"app": "openmatch",
|
||||
"component": "scale.tickets",
|
||||
})
|
||||
)
|
||||
|
||||
// Ticket generates a ticket based on the config for scale testing
|
||||
func Ticket(cfg config.View) *pb.Ticket {
|
||||
characters := cfg.GetStringSlice("testConfig.characters")
|
||||
regions := cfg.GetStringSlice("testConfig.regions")
|
||||
min := cfg.GetFloat64("testConfig.minRating")
|
||||
max := cfg.GetFloat64("testConfig.maxRating")
|
||||
latencyMap := latency(regions)
|
||||
ticket := &pb.Ticket{
|
||||
Properties: structs.Struct{
|
||||
"mmr.rating": structs.Number(normalDist(40, min, max, 20)),
|
||||
// TODO: Use string attribute value for the character attribute.
|
||||
characters[rand.Intn(len(characters))]: structs.Number(float64(time.Now().Unix())),
|
||||
}.S(),
|
||||
}
|
||||
|
||||
for _, r := range regions {
|
||||
ticket.Properties.Fields[r] = structs.Number(latencyMap[r])
|
||||
}
|
||||
|
||||
return ticket
|
||||
}
|
||||
|
||||
// latency generates a latency mapping of each region to a latency value. It picks
|
||||
// one region with latency between 0ms to 100ms and sets latencies to all other regions
|
||||
// to a value between 100ms to 300ms.
|
||||
func latency(regions []string) map[string]float64 {
|
||||
latencies := make(map[string]float64)
|
||||
for _, r := range regions {
|
||||
latencies[r] = normalDist(175, 100, 300, 75)
|
||||
}
|
||||
|
||||
latencies[regions[rand.Intn(len(regions))]] = normalDist(25, 0, 100, 75)
|
||||
return latencies
|
||||
}
|
||||
|
||||
// normalDist generates a random integer in a normal distribution
|
||||
func normalDist(avg float64, min float64, max float64, stdev float64) float64 {
|
||||
sample := (rand.NormFloat64() * stdev) + avg
|
||||
switch {
|
||||
case sample > max:
|
||||
sample = max
|
||||
case sample < min:
|
||||
sample = min
|
||||
}
|
||||
|
||||
return sample
|
||||
}
|
74
go.mod
74
go.mod
@ -14,52 +14,42 @@ module open-match.dev/open-match
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// When updating Go version, update Dockerfile.ci, Dockerfile.base-build, and go.mod
|
||||
go 1.14
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
contrib.go.opencensus.io/exporter/jaeger v0.2.1
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.7.0
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.2.0
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.13.4
|
||||
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
|
||||
cloud.google.com/go v0.40.0 // indirect
|
||||
contrib.go.opencensus.io/exporter/jaeger v0.1.0
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.5.0
|
||||
contrib.go.opencensus.io/exporter/prometheus v0.1.0
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.12.2
|
||||
contrib.go.opencensus.io/exporter/zipkin v0.1.1
|
||||
github.com/TV4/logrus-stackdriver-formatter v0.1.0
|
||||
github.com/alicebob/miniredis/v2 v2.14.1
|
||||
github.com/aws/aws-sdk-go v1.35.26 // indirect
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/go-redsync/redsync/v4 v4.3.0
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/gomodule/redigo v2.0.1-0.20191111085604-09d84710e01a+incompatible
|
||||
github.com/alicebob/miniredis/v2 v2.8.1-0.20190618082157-e29950035715
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/golang/protobuf v1.3.2
|
||||
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3
|
||||
github.com/google/gofuzz v1.0.0 // indirect
|
||||
github.com/googleapis/gnostic v0.3.1 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/imdario/mergo v0.3.11 // indirect
|
||||
github.com/pelletier/go-toml v1.8.1 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.8.0
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.6
|
||||
github.com/imdario/mergo v0.3.7 // indirect
|
||||
github.com/openzipkin/zipkin-go v0.1.6
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/prometheus/client_golang v1.0.0
|
||||
github.com/rs/xid v1.2.1
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/spf13/afero v1.4.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/viper v1.7.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.opencensus.io v0.23.0
|
||||
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 // indirect
|
||||
google.golang.org/api v0.35.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210224155714-063164c882e6
|
||||
google.golang.org/grpc v1.36.0
|
||||
google.golang.org/protobuf v1.25.1-0.20201208041424-160c7477e0e8
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/stretchr/testify v1.3.0
|
||||
go.opencensus.io v0.22.0
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
|
||||
google.golang.org/genproto v0.0.0-20190611190212-a7e196e89fd3
|
||||
google.golang.org/grpc v1.21.1
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/api v0.0.0-20191004102349-159aefb8556b // kubernetes-1.14.10
|
||||
k8s.io/apimachinery v0.0.0-20191004074956-c5d2f014d689 // kubernetes-1.14.10
|
||||
k8s.io/client-go v11.0.1-0.20191029005444-8e4128053008+incompatible // kubernetes-1.14.10
|
||||
k8s.io/klog v1.0.0 // indirect
|
||||
k8s.io/utils v0.0.0-20200729134348-d5654de09c73 // indirect
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
k8s.io/api v0.0.0-20190708094356-59223ed9f6ce // kubernetes-1.12.10
|
||||
k8s.io/apimachinery v0.0.0-20190221084156-01f179d85dbc // kubernetes-1.12.10
|
||||
k8s.io/client-go v9.0.0+incompatible // kubernetes-1.12.10
|
||||
)
|
||||
|
@ -1,148 +0,0 @@
|
||||
# Copyright 2019 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.
|
||||
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: open-match-demo
|
||||
labels:
|
||||
app: open-match-demo
|
||||
release: open-match-demo
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: om-function
|
||||
namespace: open-match-demo
|
||||
labels:
|
||||
app: open-match-customize
|
||||
component: matchfunction
|
||||
release: open-match-demo
|
||||
spec:
|
||||
selector:
|
||||
app: open-match-customize
|
||||
component: matchfunction
|
||||
release: open-match-demo
|
||||
clusterIP: None
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: grpc
|
||||
protocol: TCP
|
||||
port: 50502
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 51502
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: om-demo
|
||||
namespace: open-match-demo
|
||||
labels:
|
||||
app: open-match-demo
|
||||
component: demo
|
||||
release: open-match-demo
|
||||
spec:
|
||||
selector:
|
||||
app: open-match-demo
|
||||
component: demo
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 51507
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: om-function
|
||||
namespace: open-match-demo
|
||||
labels:
|
||||
app: open-match-customize
|
||||
component: matchfunction
|
||||
release: open-match-demo
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: open-match-customize
|
||||
component: matchfunction
|
||||
template:
|
||||
metadata:
|
||||
namespace: open-match-demo
|
||||
labels:
|
||||
app: open-match-customize
|
||||
component: matchfunction
|
||||
release: open-match-demo
|
||||
spec:
|
||||
containers:
|
||||
- name: om-function
|
||||
image: "gcr.io/open-match-public-images/openmatch-mmf-go-soloduel:0.0.0-dev"
|
||||
ports:
|
||||
- name: grpc
|
||||
containerPort: 50502
|
||||
- name: http
|
||||
containerPort: 51502
|
||||
imagePullPolicy: Always
|
||||
resources:
|
||||
requests:
|
||||
memory: 100Mi
|
||||
cpu: 100m
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: om-demo
|
||||
namespace: open-match-demo
|
||||
labels:
|
||||
app: open-match-demo
|
||||
component: demo
|
||||
release: open-match-demo
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: open-match-demo
|
||||
component: demo
|
||||
template:
|
||||
metadata:
|
||||
namespace: open-match-demo
|
||||
labels:
|
||||
app: open-match-demo
|
||||
component: demo
|
||||
release: open-match-demo
|
||||
spec:
|
||||
containers:
|
||||
- name: om-demo
|
||||
image: "gcr.io/open-match-public-images/openmatch-demo-first-match:0.0.0-dev"
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 51507
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
scheme: HTTP
|
||||
path: /healthz
|
||||
port: 51507
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
scheme: HTTP
|
||||
path: /healthz?readiness=true
|
||||
port: 51507
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
failureThreshold: 2
|
@ -1,20 +1,28 @@
|
||||
### Open Match Helm Chart Templates
|
||||
This directory contains the [helm](https://helm.sh/ "helm") chart templates used to customize and deploy Open Match.
|
||||
|
||||
Templates under the `templates/` directory are for the core components in Open Match - e.g. backend, frontend, query, synchronizor, some security policies, and configmaps are defined under this folder.
|
||||
Templates under the `templates/` directory are for the core components in Open Match - e.g. backend, frontend, mmlogic, synchronizor, some security policies, and configmaps are defined under this folder.
|
||||
|
||||
Open Match also provides templates for optional components that are disabled by default under the `subcharts/` directory.
|
||||
1. `open-match-customize` contains flexible templates to deploy your own matchfunction and evaluator.
|
||||
2. `open-match-telemetry` contains monitoring supports for Open Match, you may choose to enable/disable [jaeger](https://www.jaegertracing.io/ "jaeger"), [prometheus](http://prometheus.io "prometheus"), [stackdriver](https://cloud.google.com/stackdriver/ "stackdriver"), and [grafana](https://grafana.com/ "grafana") by overriding the config values in the provided templates.
|
||||
1. `open-match-demo` contains the template for a sample director.
|
||||
2. `open-match-customize` contains flexible templates to deploy your own matchfunction and evaluator.
|
||||
3. `open-match-telemetry` contains monitoring supports for Open Match, you may choose to enable/disable [jaeger](https://www.jaegertracing.io/ "jaeger"), [prometheus](http://prometheus.io "prometheus"), [stackdriver](https://cloud.google.com/stackdriver/ "stackdriver"), [zipkin](https://zipkin.io/ "zipkin"), and [grafana](https://grafana.com/ "grafana") by overriding the config values in the provided templates.
|
||||
4. `open-match-test` contains templates of the end-to-end in-cluster tests and distributed stress tests for Open Match.
|
||||
|
||||
You may control the behavior of Open Match by overriding the configs in `install/helm/open-match/values.yaml` file. Here are a few examples:
|
||||
|
||||
```diff
|
||||
# install/helm/open-match/values.yaml
|
||||
# 1. Configs under the `global` section affects all components - including components in the subcharts.
|
||||
# 2. Configs under the subchart name - e.g. `open-match-customize` only affects the settings in that subchart.
|
||||
# 2. Configs under the subchart name - e.g. `open-match-test` only affects the settings in that subchart.
|
||||
# 3. Otherwise, the configs are for core components (templates in the parent chart) only.
|
||||
|
||||
# Setup Open Match with customized ticket indices
|
||||
ticketIndices:
|
||||
+ - myfirstindice
|
||||
+ - mysecondice
|
||||
+ - ...
|
||||
|
||||
# Overrides spec.type of a specific Kubernetes Service
|
||||
# Equivalent helm cli flag --set swaggerui.portType=LoadBalancer
|
||||
swaggerui:
|
||||
@ -26,7 +34,7 @@ swaggerui:
|
||||
global:
|
||||
kubernetes:
|
||||
service:
|
||||
- portType: ClusterIP
|
||||
- portType: ClusterIP
|
||||
+ portType: LoadBalancer
|
||||
|
||||
# Enables grafana support in Open Match
|
||||
@ -38,8 +46,8 @@ global:
|
||||
+ enabled: true
|
||||
|
||||
# Enables an optional component in Open Match
|
||||
# Equivalent helm cli flag --set open-match-telemetry.enabled=true
|
||||
open-match-telemetry:
|
||||
# Equivalent helm cli flag --set open-match-demo.enabled=true
|
||||
open-match-demo:
|
||||
- enabled: false
|
||||
+ enabled: true
|
||||
|
||||
@ -64,4 +72,4 @@ open-match-customize:
|
||||
+ image: [YOUR_EVALUATOR_IMAGE_NAME]
|
||||
```
|
||||
|
||||
Please see [Helm - Chart Template Guide](https://helm.sh/docs/chart_template_guide/#the-chart-template-developer-s-guide "Chart Template Guide") for the advanced usages and our [Makefile](https://github.com/googleforgames/open-match/blob/master/Makefile#L358 "Makefile") for how we use the helm charts to deploy Open Match.
|
||||
Please see [Helm - Chart Template Guide](https://helm.sh/docs/chart_template_guide/#the-chart-template-developer-s-guide "Chart Template Guide") for the advanced usages and our [Makefile](https://github.com/googleforgames/open-match/blob/master/Makefile#L358 "Makefile") for how we use the helm charts to deploy Open Match.
|
@ -12,27 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: v2
|
||||
appVersion: "0.0.0-dev"
|
||||
version: 0.0.0-dev
|
||||
apiVersion: v1
|
||||
appVersion: "0.7.0"
|
||||
version: 0.7.0
|
||||
name: open-match
|
||||
dependencies:
|
||||
- name: redis
|
||||
version: 16.3.1
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
condition: open-match-core.redis.enabled
|
||||
- name: open-match-telemetry
|
||||
version: 0.0.0-dev
|
||||
condition: open-match-telemetry.enabled
|
||||
repository: "file://./subcharts/open-match-telemetry"
|
||||
- name: open-match-customize
|
||||
version: 0.0.0-dev
|
||||
condition: open-match-customize.enabled
|
||||
repository: "file://./subcharts/open-match-customize"
|
||||
- name: open-match-scale
|
||||
version: 0.0.0-dev
|
||||
condition: open-match-scale.enabled
|
||||
repository: "file://./subcharts/open-match-scale"
|
||||
description: Flexible, extensible, and scalable video game matchmaking.
|
||||
keywords:
|
||||
- kubernetes
|
||||
@ -50,3 +33,4 @@ maintainers:
|
||||
url: https://groups.google.com/forum/#!forum/open-match-discuss
|
||||
engine: gotpl
|
||||
icon: https://open-match.dev/site/images/logo.svg
|
||||
tillerVersion: ">2.10.0"
|
||||
|
Binary file not shown.
BIN
install/helm/open-match/charts/open-match-demo-0.0.0-dev.tgz
Normal file
BIN
install/helm/open-match/charts/open-match-demo-0.0.0-dev.tgz
Normal file
Binary file not shown.
Binary file not shown.
BIN
install/helm/open-match/charts/open-match-test-0.0.0-dev.tgz
Normal file
BIN
install/helm/open-match/charts/open-match-test-0.0.0-dev.tgz
Normal file
Binary file not shown.
BIN
install/helm/open-match/charts/redis-8.0.9.tgz
Normal file
BIN
install/helm/open-match/charts/redis-8.0.9.tgz
Normal file
Binary file not shown.
18
install/helm/open-match/requirements.lock
Normal file
18
install/helm/open-match/requirements.lock
Normal file
@ -0,0 +1,18 @@
|
||||
dependencies:
|
||||
- name: redis
|
||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||
version: 8.0.9
|
||||
- name: open-match-demo
|
||||
repository: file://./subcharts/open-match-demo
|
||||
version: 0.0.0-dev
|
||||
- name: open-match-telemetry
|
||||
repository: file://./subcharts/open-match-telemetry
|
||||
version: 0.0.0-dev
|
||||
- name: open-match-customize
|
||||
repository: file://./subcharts/open-match-customize
|
||||
version: 0.0.0-dev
|
||||
- name: open-match-test
|
||||
repository: file://./subcharts/open-match-test
|
||||
version: 0.0.0-dev
|
||||
digest: sha256:14faae8b59e808749ad29fc675a009daf72a3d0f22bcb7b954ba92660cb60779
|
||||
generated: "2019-08-02T15:52:25.299281406-07:00"
|
36
install/helm/open-match/requirements.yaml
Normal file
36
install/helm/open-match/requirements.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
# Copyright 2019 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.
|
||||
|
||||
dependencies:
|
||||
- name: redis
|
||||
version: 8.0.9
|
||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||
condition: open-match-core.enabled
|
||||
- name: open-match-demo
|
||||
version: 0.0.0-dev
|
||||
condition: open-match-demo.enabled
|
||||
repository: "file://./subcharts/open-match-demo"
|
||||
- name: open-match-telemetry
|
||||
version: 0.0.0-dev
|
||||
condition: open-match-telemetry.enabled
|
||||
repository: "file://./subcharts/open-match-telemetry"
|
||||
- name: open-match-customize
|
||||
version: 0.0.0-dev
|
||||
condition: open-match-customize.enabled
|
||||
repository: "file://./subcharts/open-match-customize"
|
||||
- name: open-match-test
|
||||
version: 0.0.0-dev
|
||||
condition: open-match-test.enabled
|
||||
repository: "file://./subcharts/open-match-test"
|
||||
|
@ -1,20 +0,0 @@
|
||||
{*
|
||||
Copyright 2019 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.
|
||||
*}
|
||||
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{- define "openmatchcustomize.function.hostName" -}}
|
||||
{{- .Values.function.hostName | default (printf "%s-function" (include "openmatch.fullname" . ) ) -}}
|
||||
{{- end -}}
|
@ -12,35 +12,29 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
{{- if .Values.ci }}
|
||||
|
||||
kind: Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "openmatch.fullname" . }}-test-role
|
||||
name: customize-configmap
|
||||
namespace: {{ .Release.Namespace }}
|
||||
annotations: {{- include "openmatch.chartmeta" . | nindent 4 }}
|
||||
labels:
|
||||
app: {{ template "openmatch.name" . }}
|
||||
component: config
|
||||
release: {{ .Release.Name }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- podsecuritypolicies
|
||||
resourceNames:
|
||||
- {{ include "openmatch.fullname" . }}-core-podsecuritypolicy
|
||||
verbs:
|
||||
- use
|
||||
# Grant this role get & list permission for k8s endpoints and pods resources
|
||||
# Required for e2e in-cluster testing.
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- endpoints
|
||||
- pods
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
|
||||
{{- end }}
|
||||
data:
|
||||
matchmaker_config.yaml: |-
|
||||
api:
|
||||
mmlogic:
|
||||
hostname: "{{ .Values.mmlogic.hostName }}"
|
||||
grpcport: "{{ .Values.mmlogic.grpcPort }}"
|
||||
|
||||
functions:
|
||||
hostname: "{{ .Values.function.hostName }}"
|
||||
grpcport: "{{ .Values.function.grpcPort }}"
|
||||
httpport: "{{ .Values.function.httpPort }}"
|
||||
|
||||
evaluator:
|
||||
hostname: "{{ .Values.evaluator.hostName }}"
|
||||
grpcport: "{{ .Values.evaluator.grpcPort }}"
|
||||
httpport: "{{ .Values.evaluator.httpPort }}"
|
@ -12,13 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Ugly workaround to split out MMF and evaluator
|
||||
# TODO: Reconsider helm chart structure and move things out after v0.8 release
|
||||
{{- if index .Values "evaluator" "enabled" }}
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ include "openmatch.evaluator.hostName" . }}
|
||||
name: {{ .Values.evaluator.hostName }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
annotations: {{- include "openmatch.chartmeta" . | nindent 4 }}
|
||||
labels:
|
||||
@ -30,11 +27,6 @@ spec:
|
||||
app: {{ template "openmatch.name" . }}
|
||||
component: evaluator
|
||||
release: {{ .Release.Name }}
|
||||
{{- $portType := coalesce .Values.global.kubernetes.service.portType .Values.evaluator.portType -}}
|
||||
{{- if eq $portType "ClusterIP" }}
|
||||
clusterIP: None
|
||||
{{- end }}
|
||||
type: {{ $portType }}
|
||||
ports:
|
||||
- name: grpc
|
||||
protocol: TCP
|
||||
@ -46,20 +38,20 @@ spec:
|
||||
apiVersion: autoscaling/v1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "openmatch.evaluator.hostName" . }}
|
||||
name: {{ .Values.evaluator.hostName }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
annotations: {{- include "openmatch.chartmeta" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
name: {{ include "openmatch.evaluator.hostName" . }}
|
||||
{{- include "openmatch.HorizontalPodAutoscaler.evaluator.spec.common" . | nindent 2 }}
|
||||
name: {{ .Values.evaluator.hostName }}
|
||||
{{- include "openmatch.HorizontalPodAutoscaler.spec.common" . | nindent 2 }}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "openmatch.evaluator.hostName" . }}
|
||||
name: {{ .Values.evaluator.hostName }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app: {{ template "openmatch.name" . }}
|
||||
@ -82,22 +74,20 @@ spec:
|
||||
component: evaluator
|
||||
release: {{ .Release.Name }}
|
||||
spec:
|
||||
{{- include "openmatch.labels.nodegrouping" . | nindent 6 }}
|
||||
volumes:
|
||||
{{- include "openmatch.volumes.configs" (. | merge (dict "configs" .Values.evaluatorConfigs)) | nindent 8}}
|
||||
{{- include "openmatch.volumes.configs" . | nindent 8}}
|
||||
{{- include "openmatch.volumes.tls" . | nindent 8}}
|
||||
serviceAccountName: {{ include "openmatch.serviceAccount.name" . }}
|
||||
serviceAccountName: {{ .Values.global.kubernetes.serviceAccount }}
|
||||
containers:
|
||||
- name: {{ include "openmatch.evaluator.hostName" . }}
|
||||
- name: {{ .Values.evaluator.hostName }}
|
||||
volumeMounts:
|
||||
{{- include "openmatch.volumemounts.configs" (dict "configs" .Values.evaluatorConfigs) | nindent 10 }}
|
||||
{{- include "openmatch.volumemounts.configs" . | nindent 10 }}
|
||||
{{- include "openmatch.volumemounts.tls" . | nindent 10 }}
|
||||
image: "{{ .Values.global.image.registry }}/{{ .Values.evaluator.image}}:{{ .Values.global.image.tag }}"
|
||||
image: "{{ coalesce .Values.global.image.registry .Values.image.registry }}/{{ .Values.evaluator.image}}:{{ coalesce .Values.global.image.tag .Values.image.tag }}"
|
||||
ports:
|
||||
- name: grpc
|
||||
containerPort: {{ .Values.evaluator.grpcPort }}
|
||||
- name: http
|
||||
containerPort: {{ .Values.evaluator.httpPort }}
|
||||
{{- include "openmatch.container.common" . | nindent 8 }}
|
||||
{{- end }}
|
||||
|
||||
{{- include "kubernetes.probe" (dict "port" .Values.evaluator.httpPort "isHTTPS" .Values.global.tls.enabled) | nindent 8 }}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user