├── .gitignore ├── README.md ├── asset-concepts ├── drawing.svg ├── icon.ico ├── man_PqQ_icon.ico ├── purple.png └── tile2.svg ├── build.gradle ├── docker-compose.yaml ├── envoy.yaml ├── game-client ├── Dockerfile ├── package-lock.json ├── package.json ├── src │ ├── @types │ │ └── typescript │ │ │ ├── fastbitset │ │ │ └── index.d.ts │ │ │ ├── fit-to-parent │ │ │ └── index.d.ts │ │ │ ├── reactor-core-js │ │ │ ├── index.d.ts │ │ │ └── package.json │ │ │ ├── rsocket-rpc-core │ │ │ └── index.d.ts │ │ │ └── rsocket-rpc-metrics │ │ │ └── index.d.ts │ └── main │ │ ├── resources │ │ └── public │ │ │ ├── asset │ │ │ ├── banner.png │ │ │ ├── compass-needle.png │ │ │ ├── compass.png │ │ │ ├── food1.png │ │ │ ├── food2.png │ │ │ ├── ghost.png │ │ │ ├── ghost2.png │ │ │ ├── google-play-btn.png │ │ │ ├── logo.png │ │ │ ├── loonride │ │ │ │ ├── logo-colored-small-2x.png │ │ │ │ └── logo-colored-small.png │ │ │ ├── man.old.png │ │ │ ├── man.png │ │ │ ├── man2.old.png │ │ │ ├── man2.png │ │ │ ├── pacman-sprite.png │ │ │ ├── tile.png │ │ │ └── tile2.png │ │ │ ├── css │ │ │ ├── about.css │ │ │ ├── loonride │ │ │ │ └── main.css │ │ │ ├── main.css │ │ │ ├── spinkit.css │ │ │ └── vendor │ │ │ │ └── avgrund.css │ │ │ ├── favicon.ico │ │ │ ├── fonts │ │ │ ├── nexa.otf │ │ │ └── nexa.ttf │ │ │ └── index.html │ │ └── typescript │ │ ├── Commons │ │ ├── DirectionService.ts │ │ └── SceneSupport.ts │ │ ├── Compass │ │ ├── CompassConfig.ts │ │ ├── CompassScene.ts │ │ ├── CompassService.ts │ │ └── index.ts │ │ ├── Game │ │ ├── ExtrasManager.ts │ │ ├── GameConfig.ts │ │ ├── GameScene.ts │ │ ├── GameState.ts │ │ ├── LeaderboardManager.ts │ │ ├── MapManager.ts │ │ ├── MyLocationGameService.ts │ │ ├── PlayerUtils.ts │ │ ├── PlayersManager.ts │ │ └── index.ts │ │ ├── api │ │ ├── ExtrasService.ts │ │ ├── FlowableAdapter.ts │ │ ├── GameService.ts │ │ ├── PlayerService.ts │ │ ├── grpc │ │ │ ├── ExtrasServiceClientAdapter.ts │ │ │ ├── GameServiceClientAdapter.ts │ │ │ ├── PlayerServiceClientSharedAdapter.ts │ │ │ ├── SetupServiceClientAdapter.ts │ │ │ └── index.ts │ │ ├── http │ │ │ ├── ExtrasServiceClientAdapter.ts │ │ │ ├── GameServiceClientAdapter.ts │ │ │ ├── PlayerServiceClientSharedAdapter.ts │ │ │ ├── SetupServiceClientAdapter.ts │ │ │ └── index.ts │ │ ├── rsocket │ │ │ ├── ExtrasServiceClientAdapter.ts │ │ │ ├── GameServiceClientAdapter.ts │ │ │ ├── PlayerServiceClientSharedAdapter.ts │ │ │ └── index.ts │ │ └── socket.io │ │ │ ├── ExtrasServiceClientAdapter.ts │ │ │ ├── GameServiceClientAdapter.ts │ │ │ ├── PlayerServiceClientSharedAdapter.ts │ │ │ └── index.ts │ │ ├── boot.ts │ │ └── menu.ts ├── tsconfig.json └── webpack.config.js ├── game-idl ├── build.gradle ├── package-lock.json ├── package.json ├── src │ ├── @types │ │ └── typescript │ │ │ ├── reactor-core-js │ │ │ ├── index.d.ts │ │ │ └── package.json │ │ │ └── rsocket-rpc-core │ │ │ └── index.d.ts │ └── main │ │ ├── proto │ │ ├── config.proto │ │ ├── extra.proto │ │ ├── location.proto │ │ ├── map.proto │ │ ├── player.proto │ │ ├── point.proto │ │ ├── score.proto │ │ ├── service.proto │ │ ├── size.proto │ │ ├── speed.proto │ │ └── tile.proto │ │ ├── resources │ │ └── service_rsocket_pb.d.ts.template │ │ └── typescript │ │ └── index.ts └── tsconfig.json ├── game-server ├── build.gradle ├── package-lock.json ├── package.json ├── src │ ├── main │ │ ├── @types │ │ │ └── typescript │ │ │ │ ├── express-sse │ │ │ │ └── index.d.ts │ │ │ │ ├── express │ │ │ │ └── index.d.ts │ │ │ │ ├── reactor-core-js │ │ │ │ ├── index.d.ts │ │ │ │ └── package.json │ │ │ │ └── rsocket-rpc-core │ │ │ │ └── index.d.ts │ │ ├── java │ │ │ └── org │ │ │ │ └── coinen │ │ │ │ └── reactive │ │ │ │ └── pacman │ │ │ │ ├── ReactivePacManApplication.java │ │ │ │ ├── config │ │ │ │ └── DefaultApplicationConfig.java │ │ │ │ ├── controller │ │ │ │ ├── grpc │ │ │ │ │ ├── GrpcExtrasController.java │ │ │ │ │ ├── GrpcGameController.java │ │ │ │ │ ├── GrpcLocationServiceController.java │ │ │ │ │ ├── GrpcMetricsSnapshotHandlerProxyController.java │ │ │ │ │ ├── GrpcPlayerController.java │ │ │ │ │ ├── GrpcSetupController.java │ │ │ │ │ └── config │ │ │ │ │ │ ├── GrpcGameServerConfig.java │ │ │ │ │ │ └── UUIDInterceptor.java │ │ │ │ ├── http │ │ │ │ │ ├── HttpExtrasController.java │ │ │ │ │ ├── HttpGameController.java │ │ │ │ │ ├── HttpPlayerController.java │ │ │ │ │ ├── HttpSetupController.java │ │ │ │ │ └── config │ │ │ │ │ │ └── HttpGameServerConfig.java │ │ │ │ ├── rsocket │ │ │ │ │ ├── ExtrasController.java │ │ │ │ │ ├── GameController.java │ │ │ │ │ ├── MetricsSnapshotHandlerProxyController.java │ │ │ │ │ ├── PlayerController.java │ │ │ │ │ ├── SetupController.java │ │ │ │ │ ├── config │ │ │ │ │ │ └── RSocketGameServerConfig.java │ │ │ │ │ └── support │ │ │ │ │ │ ├── ReconnectingRSocket.java │ │ │ │ │ │ └── UuidAwareRSocket.java │ │ │ │ └── socket │ │ │ │ │ └── io │ │ │ │ │ └── config │ │ │ │ │ └── SocketIOGameServerConfig.java │ │ │ │ ├── repository │ │ │ │ ├── ExtrasRepository.java │ │ │ │ ├── PlayerRepository.java │ │ │ │ ├── PowerRepository.java │ │ │ │ └── support │ │ │ │ │ ├── InMemoryExtrasRepository.java │ │ │ │ │ ├── InMemoryPlayerRepository.java │ │ │ │ │ └── InMemoryPowerRepository.java │ │ │ │ └── service │ │ │ │ ├── ExtrasService.java │ │ │ │ ├── GameService.java │ │ │ │ ├── MapService.java │ │ │ │ ├── PlayerService.java │ │ │ │ └── support │ │ │ │ ├── DefaultExtrasService.java │ │ │ │ ├── DefaultGameService.java │ │ │ │ ├── DefaultMapService.java │ │ │ │ └── DefaultPlayerService.java │ │ ├── resources │ │ │ └── application.properties │ │ └── typescript │ │ │ ├── controller │ │ │ ├── grpc │ │ │ │ ├── ExtrasController.ts │ │ │ │ ├── GameController.ts │ │ │ │ ├── LocationController.ts │ │ │ │ ├── PlayerController.ts │ │ │ │ └── SetupController.ts │ │ │ ├── http │ │ │ │ ├── HttpExtrasController.ts │ │ │ │ ├── HttpGameController.ts │ │ │ │ ├── HttpPlayerController.ts │ │ │ │ ├── HttpSetupController.ts │ │ │ │ └── index.ts │ │ │ ├── rsocket │ │ │ │ ├── ExtrasController.ts │ │ │ │ ├── GameController.ts │ │ │ │ ├── PlayerController.ts │ │ │ │ ├── SetupController.ts │ │ │ │ └── support │ │ │ │ │ └── FlowableAdapter.ts │ │ │ └── socket.io │ │ │ │ └── socketio.ts │ │ │ ├── index.ts │ │ │ ├── repository │ │ │ ├── ExtrasRepository.ts │ │ │ ├── PlayerRepository.ts │ │ │ ├── index.ts │ │ │ └── support │ │ │ │ ├── InMemoryExtrasRepository.ts │ │ │ │ └── InMemoryPlayerRepository.ts │ │ │ └── service │ │ │ ├── ExtrasService.ts │ │ │ ├── GameService.ts │ │ │ ├── MapService.ts │ │ │ ├── PlayerService.ts │ │ │ ├── index.ts │ │ │ └── support │ │ │ ├── DefaultExtrasService.ts │ │ │ ├── DefaultGameService.ts │ │ │ ├── DefaultMapService.ts │ │ │ └── DefaultPlayerService.ts │ └── test │ │ ├── java │ │ └── org │ │ │ └── coinen │ │ │ └── reactive │ │ │ └── pacman │ │ │ ├── GrpcDoser.java │ │ │ ├── RSocketRpcDoser.java │ │ │ └── SocketIODoser.java │ │ └── resources │ │ ├── log4j.properties │ │ ├── logback.xml │ │ └── logging.properties └── tsconfig.json ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lerna.json ├── metrics-client ├── build.gradle ├── package-lock.json ├── package.json ├── src │ ├── @types │ │ └── typescript │ │ │ ├── reactor-core-js │ │ │ ├── index.d.ts │ │ │ └── package.json │ │ │ ├── rsocket-rpc-core │ │ │ └── index.d.ts │ │ │ └── rsocket-rpc-metrics │ │ │ └── index.d.ts │ └── main │ │ ├── java │ │ └── org │ │ │ └── coinen │ │ │ └── reactive │ │ │ └── pacman │ │ │ └── metrics │ │ │ ├── MappingUtils.java │ │ │ ├── ReactiveMetricsRegistry.java │ │ │ ├── grpc │ │ │ ├── ClientMetricsInterceptor.java │ │ │ ├── Constants.java │ │ │ └── ServerMetricsInterceptor.java │ │ │ └── rsocket │ │ │ ├── ClientMetricsAwareRSocket.java │ │ │ ├── ReactiveMetrics.java │ │ │ ├── ServerMetricsAwareRSocket.java │ │ │ └── TimestampMetadata.java │ │ └── typescript │ │ ├── MappingUtils.ts │ │ ├── ReactiveMetricsRegistry.ts │ │ └── index.ts └── tsconfig.json ├── metrics-idl ├── build.gradle ├── package-lock.json ├── package.json ├── src │ ├── @types │ │ └── typescript │ │ │ ├── reactor-core-js │ │ │ ├── index.d.ts │ │ │ └── package.json │ │ │ └── rsocket-rpc-core │ │ │ └── index.d.ts │ └── main │ │ ├── proto │ │ ├── metrics.proto │ │ └── service.proto │ │ ├── resources │ │ ├── service_grpc_web_pb.d.ts.template │ │ └── service_rsocket_pb.d.ts.template │ │ └── typescript │ │ └── index.ts └── tsconfig.json ├── metrics-server ├── build.gradle └── src │ └── main │ ├── java │ └── org │ │ └── coinen │ │ └── reactive │ │ └── pacman │ │ └── metrics │ │ ├── ReactiveMetricsApplication.java │ │ ├── config │ │ └── DefaultApplicationConfig.java │ │ ├── controller │ │ ├── ConfigController.java │ │ ├── grpc │ │ │ ├── GrpcMetricsController.java │ │ │ └── config │ │ │ │ └── GrpcGameServerConfig.java │ │ ├── http │ │ │ └── HttpMetricsController.java │ │ ├── rsocket │ │ │ ├── FilteredMetricsController.java │ │ │ ├── MetricsController.java │ │ │ └── config │ │ │ │ └── RSocketConfig.java │ │ └── socket │ │ │ └── io │ │ │ └── SocketIOServerConfig.java │ │ └── service │ │ ├── MetricsService.java │ │ └── support │ │ ├── FastInfluxMetricsBridgeService.java │ │ ├── InfluxMetricsBridgeService.java │ │ ├── Recorder.java │ │ ├── RequestAwareSubscription.java │ │ └── UnlimitedInfluxMetricsBridgeService.java │ ├── resources │ ├── application.properties │ └── static │ │ └── config.html │ └── typescript │ ├── index.ts │ ├── lib │ ├── bestPositionFinder.ts │ ├── distance.ts │ ├── generatePlayerType.ts │ ├── processors │ │ ├── extrasProcessor.ts │ │ ├── index.ts │ │ ├── playersProcessor.ts │ │ └── scoreProcessor.ts │ └── services │ │ ├── extraService.ts │ │ ├── gameService.ts │ │ ├── index.ts │ │ ├── playerService.ts │ │ └── scoreService.ts │ ├── maze.ts │ ├── store.ts │ └── tile.ts ├── package-lock.json ├── package.json ├── scripts ├── copy-template.js ├── grpc-web-plugin-loader.js ├── make-local-generated-dir.js └── postprocess-generated-ts.js └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .vscode 4 | 5 | **/dist 6 | !dist/index.html 7 | 8 | src/shared/* 9 | !src/shared/.gitkeep 10 | 11 | HELP.md 12 | .gradle 13 | build 14 | out 15 | !gradle/wrapper/gradle-wrapper.jar 16 | 17 | ### Project ### 18 | 19 | **/generated/ 20 | 21 | ### STS ### 22 | .apt_generated 23 | .classpath 24 | .factorypath 25 | .project 26 | .settings 27 | .springBeans 28 | .sts4-cache 29 | 30 | ### IntelliJ IDEA ### 31 | .idea 32 | *.iws 33 | *.iml 34 | *.ipr 35 | /out/ 36 | 37 | ### NetBeans ### 38 | /nbproject/private/ 39 | /nbbuild/ 40 | /dist/ 41 | /nbdist/ 42 | /.nb-gradle/ 43 | 44 | influxdb/ 45 | chronograf/ 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REACTIVE PAC-MAN 2 | 3 | POWERED BY [RSOCKET](http://rsocket.io). 4 | DEVELOPED BY [Oleh Dokuka](https://twitter.com/OlehDokuka), [Danil Drobot](https://github.com/daniildxb), [Alan Scherger](https://twitter.com/flyinprogrammer). 5 | 6 | [NETIFI, INC](https://www.netifi.com). 7 | 8 | ## BUILD 9 | 10 | ### Prerequisits: 11 | 12 | * Protobuf 3.7.0 13 | * Java 11 14 | * Node + NPM 15 | 16 | 17 | ### Scripts: 18 | 19 | ``` 20 | ./gradlew clean build 21 | npm install 22 | ``` 23 | -------------------------------------------------------------------------------- /asset-concepts/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/asset-concepts/icon.ico -------------------------------------------------------------------------------- /asset-concepts/man_PqQ_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/asset-concepts/man_PqQ_icon.ico -------------------------------------------------------------------------------- /asset-concepts/purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/asset-concepts/purple.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.2.6.RELEASE' apply false 3 | id 'com.bmuschko.docker-spring-boot-application' version '6.4.0' apply false 4 | id 'com.google.protobuf' version '0.8.12' apply false 5 | } 6 | 7 | subprojects { 8 | 9 | apply plugin: 'io.spring.dependency-management' 10 | apply plugin: 'java' 11 | apply plugin: 'idea' 12 | 13 | group = 'org.coinen.reactive.pacman' 14 | version = '0.0.1' 15 | sourceCompatibility = '11' 16 | 17 | repositories { 18 | jcenter() 19 | mavenCentral() 20 | maven { url 'https://jitpack.io' } 21 | maven { url 'https://repo.spring.io/snapshot' } 22 | maven { url 'https://repo.spring.io/milestone' } 23 | maven { url 'https://oss.jfrog.org/oss-snapshot-local' } 24 | maven { url "https://oss.sonatype.org/content/repositories/snapshots" } 25 | } 26 | 27 | dependencyManagement { 28 | imports { 29 | mavenBom 'org.springframework.boot:spring-boot-dependencies:2.2.6.RELEASE' 30 | mavenBom 'io.rsocket:rsocket-bom:1.0.0-RC7' 31 | mavenBom 'com.google.protobuf:protobuf-bom:3.11.4' 32 | } 33 | 34 | dependencies { 35 | 36 | dependencySet (group: 'io.grpc', version: '1.28.1') { 37 | entry 'grpc-stub' 38 | entry 'grpc-netty' 39 | entry 'grpc-okhttp' 40 | entry 'grpc-protobuf' 41 | entry 'protoc-gen-grpc-java' 42 | } 43 | 44 | dependencySet (group: 'io.rsocket.rpc', version: '0.2.18') { 45 | entry 'rsocket-rpc-core' 46 | entry 'rsocket-rpc-protobuf' 47 | entry 'rsocket-rpc-protobuf-idl' 48 | } 49 | 50 | 51 | dependency group: 'com.corundumstudio.socketio', name: 'netty-socketio', version: '1.7.17' 52 | dependency group: 'io.socket', name: 'socket.io-client', version: '1.0.0' 53 | 54 | dependency group: 'org.roaringbitmap', name: 'RoaringBitmap', version: '0.7.42' 55 | 56 | dependency group: 'com.google.protobuf', name: 'protoc', version: '3.11.4' 57 | 58 | dependency group: 'org.jctools', name: 'jctools-core', version: '3.0.0' 59 | dependency group: 'com.salesforce.servicelibs', name: 'reactor-grpc-stub', version: '0.10.0' 60 | 61 | dependency group:'io.github.lognet', name: 'grpc-spring-boot-starter', version: '3.3.0' 62 | dependency group: 'com.github.collaborationinencapsulation.spring-boot-rsocket', name: 'spring-boot-starter-rsocket', version: 'dec35e4a10' 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | 3 | services: 4 | game-client: 5 | build: ./game-client 6 | image: "reactive-game-client:0.0.1" 7 | ports: 8 | - "9000:80" 9 | depends_on: 10 | - game-server 11 | game-server: 12 | image: "reactive-pacman-game-server:0.0.1" 13 | ports: 14 | - "3000:3000" 15 | - "9090:9090" 16 | depends_on: 17 | - envoy 18 | - metrics-server 19 | environment: 20 | GRPC_METRICSENDPOINT: ws://host.docker.internal:9095 21 | HTTP_METRICSENDPOINT: http://host.docker.internal:4000 22 | RSOCKET_METRICSENDPOINT: ws://host.docker.internal:4000 23 | metrics-server: 24 | image: "reactive-pacman-metrics-server:0.0.1" 25 | ports: 26 | - "4000:4000" 27 | - "9095:9095" 28 | environment: 29 | MANAGEMENT_METRICS_EXPORT_INFLUX_URI: http://host.docker.internal:8086 30 | envoy: 31 | image: "envoyproxy/envoy:latest" 32 | hostname: "envoy" 33 | ports: 34 | - "8000:8080" 35 | volumes: 36 | - "./envoy.yaml:/etc/envoy/envoy.yaml" 37 | entrypoint: "/usr/local/bin/envoy -c /etc/envoy/envoy.yaml" 38 | influxdb: 39 | image: "influxdb" 40 | volumes: 41 | # Mount for influxdb data directory 42 | - "./influxdb/data:/var/lib/influxdb" 43 | # Mount for influxdb configuration 44 | - "./influxdb/config/:/etc/influxdb/" 45 | ports: 46 | # The API for InfluxDB is served on port 8086 47 | - "8086:8086" 48 | - "8082:8082" 49 | # UDP Port 50 | - "8089:8089" 51 | chronograf: 52 | image: "chronograf" 53 | volumes: 54 | # Mount for chronograf database 55 | - "./chronograf/data/:/var/lib/chronograf/" 56 | ports: 57 | # The WebUI for Chronograf is served on port 8888 58 | - "8888:8888" 59 | depends_on: 60 | - influxdb 61 | command: "--influxdb-url=http://host.docker.internal:8086" 62 | -------------------------------------------------------------------------------- /envoy.yaml: -------------------------------------------------------------------------------- 1 | admin: 2 | access_log_path: /tmp/admin_access.log 3 | address: 4 | socket_address: { address: 0.0.0.0, port_value: 9901 } 5 | 6 | static_resources: 7 | listeners: 8 | - name: listener_0 9 | address: 10 | socket_address: { address: 0.0.0.0, port_value: 8080 } 11 | filter_chains: 12 | - filters: 13 | - name: envoy.http_connection_manager 14 | config: 15 | codec_type: auto 16 | stat_prefix: ingress_http 17 | route_config: 18 | name: local_route 19 | virtual_hosts: 20 | - name: local_service 21 | domains: ["*"] 22 | routes: 23 | - match: { prefix: "/" } 24 | route: 25 | cluster: greeter_service 26 | max_grpc_timeout: 0s 27 | cors: 28 | allow_origin: 29 | - "*" 30 | allow_methods: GET, PUT, DELETE, POST, OPTIONS 31 | allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout,uuid 32 | max_age: "1728000" 33 | expose_headers: custom-header-1,grpc-status,grpc-message 34 | enabled: true 35 | http_filters: 36 | - name: envoy.grpc_web 37 | - name: envoy.cors 38 | - name: envoy.router 39 | clusters: 40 | - name: greeter_service 41 | connect_timeout: 0.25s 42 | type: logical_dns 43 | http2_protocol_options: {} 44 | lb_policy: round_robin 45 | hosts: [{ socket_address: { address: host.docker.internal, port_value: 9090 }}] -------------------------------------------------------------------------------- /game-client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | COPY build/ /usr/share/nginx/html/ 4 | -------------------------------------------------------------------------------- /game-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "game-client", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "npm run build", 8 | "build": "webpack", 9 | "start:dev": "webpack-dev-server" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "game-idl": "^0.0.1", 15 | "metrics-idl": "^0.0.1", 16 | "metrics-client": "^0.0.1", 17 | "@types/rsocket-types": "^0.0.2", 18 | "@types/rsocket-core": "^0.0.5", 19 | "@types/rsocket-flowable": "^0.0.5", 20 | "@types/rsocket-websocket-client": "^0.0.3", 21 | "@types/socket.io-client": "^1.4.32", 22 | "@types/express": "^4.17.6", 23 | "@types/google-protobuf": "^3.7.2", 24 | "@types/jquery": "^3.3.36", 25 | "@types/uuid": "^7.0.3", 26 | "fit-to-parent": "^1.3.61", 27 | "jquery": "^3.5.0", 28 | "phaser": "^3.23.0", 29 | "google-protobuf": "^3.11.4", 30 | "grpc-web": "^1.0.7", 31 | "reactor-core-js": "^0.5.0", 32 | "rsocket-flowable": "^0.0.14", 33 | "rsocket-types": "^0.0.16", 34 | "rsocket-websocket-client": "0.0.19", 35 | "rsocket-rpc-core": "^0.1.6", 36 | "rsocket-rpc-metrics": "^0.1.6", 37 | "rsocket-rpc-tracing": "^0.1.6", 38 | "socket.io-client": "^2.3.0", 39 | "uuid": "^7.0.3" 40 | }, 41 | "devDependencies": { 42 | "compression-webpack-plugin": "^3.1.0", 43 | "@grpc/proto-loader": "^0.5.4", 44 | "async": "^3.2.0", 45 | "copy-webpack-plugin": "^5.1.1", 46 | "css-loader": "^3.5.3", 47 | "html-webpack-plugin": "^4.2.1", 48 | "terser-webpack-plugin": "^2.3.6", 49 | "livereload": "^0.9.1", 50 | "style-loader": "^1.2.1", 51 | "ts-loader": "^7.0.1", 52 | "ts-protoc-gen": "^0.12.0", 53 | "typescript": "^3.8.3", 54 | "webpack": "^4.43.0", 55 | "webpack-cli": "^3.3.11", 56 | "webpack-dev-server": "^3.10.3" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /game-client/src/@types/typescript/fit-to-parent/index.d.ts: -------------------------------------------------------------------------------- 1 | interface JQuery { 2 | fitToParent() : any; 3 | } -------------------------------------------------------------------------------- /game-client/src/@types/typescript/reactor-core-js/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'reactor-core-js/flux' { 2 | import { Disposable } from "reactor-core-js"; 3 | import { Publisher, Subscriber, Subscription } from "reactor-core-js/reactive-streams-spec"; 4 | 5 | export class Flux implements Publisher { 6 | subscribe(s: Subscriber): void; 7 | 8 | static from(stream: Publisher): Flux; 9 | 10 | 11 | 12 | 13 | map (mapper: (t: T) => V): Flux; 14 | doOnNext(callback: (t: T) => void): Flux; 15 | 16 | 17 | 18 | compose(transformer: (flux: Flux) => Publisher): Flux 19 | static mergeArray(sources: Flux[]): Flux; 20 | consume(): Disposable; 21 | consume(onNextCallback: (t: T) => void): Disposable; 22 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void): Disposable; 23 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void, onCompleteCallback: () => void): Disposable; 24 | } 25 | 26 | export class DirectProcessor extends Flux implements Subscriber { 27 | onSubscribe(s: Subscription): void; 28 | onNext(t: T): void; 29 | onError(t: Error): void; 30 | onComplete(): void; 31 | } 32 | // } 33 | 34 | // declare module 'reactor-core-js/mono' { 35 | 36 | export class Mono implements Publisher { 37 | subscribe(): void 38 | } 39 | } 40 | 41 | declare module 'reactor-core-js' { 42 | export interface Disposable { 43 | dispose(): void; 44 | } 45 | } 46 | 47 | declare module 'reactor-core-js/reactive-streams-spec' { 48 | export interface Subscription { 49 | request(n: number): void; 50 | cancel(): void; 51 | } 52 | 53 | export interface Subscriber { 54 | onSubscribe(s: Subscription): void; 55 | onNext(t: T): void; 56 | onError(t: Error): void; 57 | onComplete(): void; 58 | } 59 | 60 | export interface Publisher { 61 | subscribe(s: Subscriber): void; 62 | } 63 | 64 | export interface Processor extends Subscriber, Publisher { } 65 | } -------------------------------------------------------------------------------- /game-client/src/@types/typescript/reactor-core-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "@types/reactor-core-js@*", 3 | "_id": "@types/reactor-core-js@4.16.0", 4 | "_inBundle": false, 5 | "_integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==", 6 | "_location": "/@types/reactor-core-js", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "@types/reactor-core-js@*", 12 | "name": "@types/reactor-core-js", 13 | "escapedName": "@types%2freactor-core-js", 14 | "scope": "@types", 15 | "rawSpec": "*", 16 | "saveSpec": null, 17 | "fetchSpec": "*" 18 | }, 19 | "_requiredBy": [ 20 | "/@types/express", 21 | "/@types/serve-static" 22 | ], 23 | "_resolved": "https://registry.npmjs.org/@types/reactor-core-js/-/reactor-core-js-4.16.0.tgz", 24 | "_shasum": "fdfe777594ddc1fe8eb8eccce52e261b496e43e7", 25 | "_spec": "@types/reactor-core-js@*", 26 | "_where": "/Users/Shadowgun/Documents/Workspace/Java/dinoman-io/node_modules/@types/express", 27 | "bugs": { 28 | "url": "https://github.com/DefinitelyTyped/DefinitelyTyped/issues" 29 | }, 30 | "bundleDependencies": false, 31 | "contributors": [ 32 | { 33 | "name": "Boris Yankov", 34 | "url": "https://github.com/borisyankov" 35 | }, 36 | { 37 | "name": "Michał Lytek", 38 | "url": "https://github.com/19majkel94" 39 | }, 40 | { 41 | "name": "Kacper Polak", 42 | "url": "https://github.com/kacepe" 43 | }, 44 | { 45 | "name": "Satana Charuwichitratana", 46 | "url": "https://github.com/micksatana" 47 | }, 48 | { 49 | "name": "Sami Jaber", 50 | "url": "https://github.com/samijaber" 51 | } 52 | ], 53 | "dependencies": { 54 | "@types/events": "*", 55 | "@types/node": "*", 56 | "@types/range-parser": "*" 57 | }, 58 | "deprecated": false, 59 | "description": "TypeScript definitions for Express", 60 | "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped#readme", 61 | "license": "MIT", 62 | "main": "", 63 | "name": "@types/reactor-core-js", 64 | "repository": { 65 | "type": "git", 66 | "url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git" 67 | }, 68 | "scripts": {}, 69 | "typeScriptVersion": "2.2", 70 | "typesPublisherContentHash": "8be76390251659a06700a35c7e64af43c3207cbd3c16a34cc7491535b5535aa8", 71 | "version": "4.16.0" 72 | } 73 | -------------------------------------------------------------------------------- /game-client/src/@types/typescript/rsocket-rpc-core/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'rsocket-rpc-core' { 2 | import { PayloadSerializers } from "rsocket-core"; 3 | import { Encodable, DuplexConnection, Responder, ReactiveSocket, Payload } from "rsocket-types"; 4 | import { Single, Flowable } from "rsocket-flowable"; 5 | 6 | export type ClientConfig = { 7 | serializers?: PayloadSerializers, 8 | setup: { 9 | keepAlive: number, 10 | lifetime: number, 11 | metadata?: Encodable, 12 | }, 13 | transport: DuplexConnection, 14 | responder?: Responder, 15 | } 16 | 17 | export class RpcClient { 18 | constructor(config: ClientConfig); 19 | close(): void; 20 | connect(): Single>; 21 | } 22 | 23 | export class RequestHandlingRSocket implements Responder { 24 | 25 | addService(service: string, handler: Responder): void; 26 | 27 | fireAndForget(payload: Payload): void; 28 | 29 | requestResponse(payload: Payload,): Single> 30 | 31 | requestStream(payload: Payload,): Flowable>; 32 | 33 | requestChannel(payloads: Flowable>,): Flowable>; 34 | 35 | metadataPush(payload: Payload): Single; 36 | } 37 | } -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/banner.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/compass-needle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/compass-needle.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/compass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/compass.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/food1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/food1.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/food2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/food2.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/ghost.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/ghost2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/ghost2.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/google-play-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/google-play-btn.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/logo.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/loonride/logo-colored-small-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/loonride/logo-colored-small-2x.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/loonride/logo-colored-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/loonride/logo-colored-small.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/man.old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/man.old.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/man.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/man2.old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/man2.old.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/man2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/man2.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/pacman-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/pacman-sprite.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/tile.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/asset/tile2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/asset/tile2.png -------------------------------------------------------------------------------- /game-client/src/main/resources/public/css/about.css: -------------------------------------------------------------------------------- 1 | #tutorials { 2 | padding-top: 0px !important; 3 | } -------------------------------------------------------------------------------- /game-client/src/main/resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/favicon.ico -------------------------------------------------------------------------------- /game-client/src/main/resources/public/fonts/nexa.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/fonts/nexa.otf -------------------------------------------------------------------------------- /game-client/src/main/resources/public/fonts/nexa.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/game-client/src/main/resources/public/fonts/nexa.ttf -------------------------------------------------------------------------------- /game-client/src/main/typescript/Commons/SceneSupport.ts: -------------------------------------------------------------------------------- 1 | export default interface SceneSupport { 2 | 3 | update(time: number, deltaTime: number): void; 4 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/Compass/CompassConfig.ts: -------------------------------------------------------------------------------- 1 | import { GameConfig, GameState, MyLocationGameService } from "../Game"; 2 | import PlayerService from "../api/PlayerService"; 3 | 4 | export default interface CompassConfig { 5 | readonly config: GameConfig; 6 | readonly state: GameState; 7 | readonly playerService: PlayerService; 8 | readonly locationService: MyLocationGameService; 9 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/Compass/CompassScene.ts: -------------------------------------------------------------------------------- 1 | import { Scene } from 'phaser'; 2 | import CompassConfig from './CompassConfig'; 3 | import CompassService from './CompassService'; 4 | 5 | export default class CompassScene extends Scene { 6 | private service: CompassService; 7 | private compass: Phaser.GameObjects.Image; 8 | private compassNeedle: Phaser.GameObjects.Image; 9 | 10 | constructor() { 11 | super('Compass'); 12 | } 13 | 14 | create(config: CompassConfig) { 15 | this.compass = this.add.image(60, 60, 'compass').setScale(0.6 * config.config.scale); 16 | this.compassNeedle = this.add.image(60, 60, 'compass-needle').setScale(0.6 * config.config.scale); 17 | this.service = new CompassService(config.playerService, config.locationService, config.state); 18 | } 19 | 20 | destroy() { 21 | this.service.dispose() 22 | } 23 | 24 | update() { 25 | const { rotation } = this.service; 26 | 27 | if (rotation == undefined) { 28 | this.compassNeedle.setRotation((Date.now() / 350) % 360); 29 | } 30 | else { 31 | this.compassNeedle.setRotation(rotation + Math.PI / 2 + Math.PI); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/Compass/index.ts: -------------------------------------------------------------------------------- 1 | import CompassConfig from "./CompassConfig"; 2 | import CompassScene from "./CompassScene"; 3 | 4 | 5 | export { CompassConfig, CompassScene }; -------------------------------------------------------------------------------- /game-client/src/main/typescript/Game/ExtrasManager.ts: -------------------------------------------------------------------------------- 1 | import GameState from "./GameState"; 2 | import GameConfig from "./GameConfig"; 3 | import ExtrasService from "../api/ExtrasService"; 4 | import { Extra } from "game-idl"; 5 | import SceneSupport from "../Commons/SceneSupport"; 6 | 7 | export default class ExtrasManager implements SceneSupport { 8 | extra: Set; 9 | extraSprites: Map; 10 | 11 | constructor( 12 | private scene: Phaser.Scene, 13 | private state: GameState, 14 | private config: GameConfig, 15 | extras: Array, 16 | extraService: ExtrasService, 17 | ) { 18 | this.extra = new Set(); 19 | this.extraSprites = new Map(); 20 | extras.forEach(extra => this.insertExtra(extra)); 21 | extraService.extras() 22 | .consume(e => this.doOnExtra(e)); 23 | } 24 | 25 | currentTimeout: any; 26 | 27 | doOnExtra(extra: Extra.AsObject) { 28 | this.retainExtra(extra.last); 29 | this.insertExtra(extra.current); 30 | 31 | if (Math.sign(extra.last) === -1) { 32 | if (this.state.powerState > 0) { 33 | clearTimeout(this.currentTimeout); 34 | } 35 | 36 | this.state.powerState = 1; 37 | this.currentTimeout = setTimeout(() => { 38 | this.state.powerState = 2; 39 | this.currentTimeout = setTimeout(() => { 40 | this.state.powerState = 0; 41 | }, 3000); 42 | }, 7000); 43 | } 44 | } 45 | 46 | retainExtra(position: number) { 47 | if (this.extra.has(position)) { 48 | this.extra.delete(position); 49 | 50 | const normalizedPosition = Math.abs(position); 51 | 52 | this.extraSprites.get(normalizedPosition).destroy(); 53 | this.extraSprites.delete(normalizedPosition); 54 | } 55 | } 56 | 57 | // extra < 0 == powerUp 58 | 59 | insertExtra(position: number) { 60 | const normalizedPosition = Math.abs(position); 61 | const { map: { width }, size, scale } = this.config; 62 | const i = normalizedPosition % width; 63 | const j = Math.floor(normalizedPosition / width); 64 | const sprite = this.scene.physics.add 65 | .sprite( 66 | i * size, 67 | j * size, 68 | 'food' + (Math.sign(position) === 1 ? '1' : '2') 69 | ) 70 | .setScale(scale); 71 | this.extraSprites.set(normalizedPosition, sprite); 72 | this.extra.add(position); 73 | } 74 | 75 | update(): void { } 76 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/Game/GameConfig.ts: -------------------------------------------------------------------------------- 1 | export default interface Config { 2 | readonly zoom: number; 3 | readonly size: number; 4 | readonly scale: number; 5 | readonly map: { 6 | readonly width: number; 7 | readonly height: number; 8 | }; 9 | readonly screen: { 10 | readonly width: number; 11 | readonly height: number; 12 | }; 13 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/Game/GameState.ts: -------------------------------------------------------------------------------- 1 | import { Player } from "game-idl"; 2 | import { Tile } from "game-idl"; 3 | 4 | export default class GameState { 5 | tiles: Tile.AsObject[]; 6 | 7 | powerState: number; 8 | player: Player.AsObject; 9 | players: { [key: string]: Player.AsObject }; 10 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/Game/MyLocationGameService.ts: -------------------------------------------------------------------------------- 1 | import { Location } from "game-idl"; 2 | import { Flux } from "reactor-core-js/flux"; 3 | 4 | export default class MyLocationGameService { 5 | 6 | constructor(private locationStream: Flux) { } 7 | 8 | playerLocation(): Flux { 9 | return this.locationStream; 10 | } 11 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/Game/index.ts: -------------------------------------------------------------------------------- 1 | import GameConfig from "./GameConfig"; 2 | import GameState from "./GameState"; 3 | import MyLocationGameService from "./MyLocationGameService"; 4 | import Scene from "./GameScene"; 5 | 6 | export { GameConfig, GameState, MyLocationGameService, Scene as GameScene }; -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/ExtrasService.ts: -------------------------------------------------------------------------------- 1 | import { Flux } from "reactor-core-js/flux"; 2 | import { Extra } from "game-idl"; 3 | 4 | export default interface ExtrasService { 5 | 6 | extras(): Flux; 7 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/FlowableAdapter.ts: -------------------------------------------------------------------------------- 1 | import { Publisher, Subscriber, Subscription } from "reactor-core-js/reactive-streams-spec"; 2 | 3 | export default class FlowableAdapter implements Publisher { 4 | 5 | static wrap(source: Publisher): FlowableAdapter { 6 | return new FlowableAdapter(source); 7 | } 8 | 9 | constructor(private source: Publisher) {} 10 | 11 | subscribe(s: Subscriber): void { 12 | this.source.subscribe({ 13 | onSubscribe: (subscription: Subscription) => { 14 | s.onSubscribe({ 15 | request: (n) => subscription.request(n > Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : n), 16 | cancel: () => subscription.cancel() 17 | }) 18 | }, 19 | onNext: (t: S) => s.onNext(t), 20 | onError: (e: Error) => s.onError(e), 21 | onComplete: () => s.onComplete(), 22 | }) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/GameService.ts: -------------------------------------------------------------------------------- 1 | import { Config } from "game-idl"; 2 | import { Nickname } from "game-idl"; 3 | import { Single } from "rsocket-flowable"; 4 | 5 | 6 | export default interface GameService { 7 | 8 | start(nickname: Nickname.AsObject): Single 9 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/PlayerService.ts: -------------------------------------------------------------------------------- 1 | import { Flux } from "reactor-core-js/flux"; 2 | import { Single } from "rsocket-flowable"; 3 | import { Player } from "game-idl"; 4 | import { Location } from "game-idl"; 5 | 6 | export default interface PlayerService { 7 | 8 | locate(locationStream: Flux): Single 9 | 10 | players(): Flux; 11 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/grpc/ExtrasServiceClientAdapter.ts: -------------------------------------------------------------------------------- 1 | import ExtrasService from "../ExtrasService"; 2 | import { Extra } from "game-idl"; 3 | import { Flux } from "reactor-core-js/flux"; 4 | import { Empty } from "google-protobuf/google/protobuf/empty_pb"; 5 | import FlowableAdapter from "../FlowableAdapter"; 6 | import { GRPCWebServices } from "game-idl"; 7 | import { Flowable } from "rsocket-flowable"; 8 | 9 | export default class ExtrasServiceClientAdapter implements ExtrasService { 10 | 11 | private service: GRPCWebServices.ExtrasServiceClient; 12 | 13 | constructor() { 14 | const urlParams = new URLSearchParams(window.location.search); 15 | const endpoint = urlParams.get('endpoint'); 16 | this.service = new GRPCWebServices.ExtrasServiceClient(endpoint || "http://dinoman.netifi.com:8000", {}, {}); 17 | } 18 | 19 | extras(): Flux { 20 | return Flux.from(FlowableAdapter.wrap(new Flowable(subscriber => { 21 | const clientReadableStream = this.service.extras(new Empty(), { "uuid": localStorage.getItem("uuid") }); 22 | 23 | subscriber.onSubscribe({ 24 | request: (): void => { }, 25 | cancel: (): void => clientReadableStream.cancel() 26 | }); 27 | 28 | clientReadableStream.on("data", response => subscriber.onNext(response)); 29 | clientReadableStream.on("end", () => subscriber.onComplete()); 30 | clientReadableStream.on("error", (err) => subscriber.onError(new Error(`An Grpc Error was thrown. Code: [${err.code}]. Message: ${err.message}`))); 31 | }))) 32 | .map(player => player.toObject()) 33 | } 34 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/grpc/GameServiceClientAdapter.ts: -------------------------------------------------------------------------------- 1 | import GameService from "../GameService"; 2 | import { Nickname } from "game-idl"; 3 | import { Config } from "game-idl"; 4 | import { Single } from "rsocket-flowable"; 5 | import { GRPCWebServices } from "game-idl"; 6 | import { ClientReadableStream } from "grpc-web"; 7 | 8 | export default class GameServiceClientAdapter implements GameService { 9 | 10 | private service: GRPCWebServices.GameServiceClient; 11 | 12 | constructor() { 13 | const urlParams = new URLSearchParams(window.location.search); 14 | const endpoint = urlParams.get('endpoint'); 15 | this.service = new GRPCWebServices.GameServiceClient(endpoint || "http://dinoman.netifi.com:8000", {}, {}); 16 | } 17 | 18 | start({ value }: Nickname.AsObject): Single { 19 | const nicknameProto = new Nickname(); 20 | 21 | nicknameProto.setValue(value); 22 | 23 | return new Single(subject => { 24 | let stream: ClientReadableStream; 25 | subject.onSubscribe(undefined); //TODO: FIXME 26 | stream = this.service.start(nicknameProto as any, { "uuid": localStorage.getItem("uuid") }, (err, response) => { 27 | if (err) { 28 | subject.onError(new Error(`An Grpc Error was thrown. Code: [${err.code}]. Message: ${err.message}`)); 29 | return; 30 | } 31 | subject.onComplete((response.toObject() as any) as Config.AsObject); 32 | }); 33 | }) 34 | } 35 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/grpc/SetupServiceClientAdapter.ts: -------------------------------------------------------------------------------- 1 | import { Single } from "rsocket-flowable"; 2 | import { Map } from "game-idl"; 3 | import { GRPCWebServices } from "game-idl"; 4 | import { Empty } from "google-protobuf/google/protobuf/empty_pb"; 5 | import { ClientReadableStream } from "grpc-web"; 6 | import * as uuid from "uuid"; 7 | 8 | export default class SetupServiceClientAdapter { 9 | 10 | private service: GRPCWebServices.SetupServiceClient; 11 | 12 | constructor() { 13 | const urlParams = new URLSearchParams(window.location.search); 14 | const endpoint = urlParams.get('endpoint'); 15 | this.service = new GRPCWebServices.SetupServiceClient(endpoint || "http://dinoman.netifi.com:8000", {}, {}); 16 | } 17 | 18 | map(): Single { 19 | return new Single(subject => { 20 | let stream: ClientReadableStream; 21 | const myId = uuid.v4(); 22 | localStorage.setItem("uuid", myId) 23 | subject.onSubscribe(() => stream.cancel()); 24 | stream = this.service.get(new Empty(), {"uuid": myId}, (err, response) => { 25 | if (err) { 26 | subject.onError(new Error(`An Grpc Error was thrown. Code: [${err.code}]. Message: ${err.message}`)); 27 | return; 28 | } 29 | subject.onComplete((response.toObject() as any) as Map.AsObject); 30 | }); 31 | }); 32 | } 33 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/grpc/index.ts: -------------------------------------------------------------------------------- 1 | import ExtrasServiceClientAdapter from "./ExtrasServiceClientAdapter"; 2 | import PlayerServiceClientSharedAdapter from "./PlayerServiceClientSharedAdapter"; 3 | import GameServiceClientAdapter from "./GameServiceClientAdapter"; 4 | import SetupServiceClientAdapter from "./SetupServiceClientAdapter"; 5 | 6 | export { 7 | ExtrasServiceClientAdapter, 8 | GameServiceClientAdapter, 9 | PlayerServiceClientSharedAdapter, 10 | SetupServiceClientAdapter 11 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/http/ExtrasServiceClientAdapter.ts: -------------------------------------------------------------------------------- 1 | import ExtrasService from "../ExtrasService"; 2 | import FlowableAdapter from "../FlowableAdapter"; 3 | import { Extra } from "game-idl"; 4 | import { Flux } from "reactor-core-js/flux"; 5 | import {Flowable} from "rsocket-flowable"; 6 | 7 | export default class ExtrasServiceClientAdapter implements ExtrasService { 8 | 9 | extras(): Flux { 10 | const urlParams = new URLSearchParams(window.location.search); 11 | const endpoint = urlParams.get('endpoint'); 12 | return Flux.from(FlowableAdapter.wrap(new Flowable(subscriber => { 13 | const eventSource = new EventSource(`${endpoint || "http://dinoman.netifi.com:3000"}/http/extras`, { withCredentials : true }); 14 | 15 | subscriber.onSubscribe({ 16 | request: (): void => {}, 17 | cancel: (): void => eventSource.close() 18 | }); 19 | 20 | eventSource.onmessage = e => { 21 | subscriber.onNext(Extra.deserializeBinary(new Uint8Array(eval(e.data)))); 22 | }; 23 | 24 | eventSource.onerror = (e: any) => { 25 | subscriber.onError(e.data); 26 | } 27 | }))) 28 | .map(extra => extra.toObject()); 29 | } 30 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/http/GameServiceClientAdapter.ts: -------------------------------------------------------------------------------- 1 | import GameService from "../GameService"; 2 | import { Nickname } from "game-idl"; 3 | import { Config } from "game-idl"; 4 | import { Single } from "rsocket-flowable"; 5 | 6 | export default class GameServiceClientAdapter implements GameService { 7 | 8 | start({ value }: Nickname.AsObject): Single { 9 | const urlParams = new URLSearchParams(window.location.search); 10 | const endpoint = urlParams.get('endpoint'); 11 | const nicknameProto = new Nickname(); 12 | 13 | nicknameProto.setValue(value); 14 | 15 | return new Single(subject => { 16 | subject.onSubscribe(undefined);//TODO: FIXME 17 | fetch(`${endpoint || "http://dinoman.netifi.com:3000"}/http/start`, { 18 | method: "POST", 19 | body: nicknameProto.serializeBinary(), 20 | credentials: "include" 21 | }) 22 | .then(response => response.arrayBuffer()) 23 | .then(buffer => subject.onComplete(Config.deserializeBinary(new Uint8Array(buffer)).toObject()), error => subject.onError(error)) 24 | }) 25 | } 26 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/http/SetupServiceClientAdapter.ts: -------------------------------------------------------------------------------- 1 | import { Single } from "rsocket-flowable"; 2 | import { Map } from "game-idl"; 3 | 4 | export default class SetupServiceClientAdapter { 5 | 6 | map(): Single { 7 | const urlParams = new URLSearchParams(window.location.search); 8 | const endpoint = urlParams.get('endpoint'); 9 | return new Single(subject => { 10 | subject.onSubscribe(undefined); //TODO: FIXME 11 | fetch(`${endpoint || "http://dinoman.netifi.com:3000"}/http/setup`, { 12 | credentials: "include" 13 | }) 14 | .then(res => { 15 | console.log('got res', res.body); 16 | return res; 17 | }) 18 | .then(response => response.arrayBuffer()) 19 | .then(buffer => subject.onComplete(Map.deserializeBinary(new Uint8Array(buffer)).toObject()), error => subject.onError(error)) 20 | }) 21 | } 22 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/http/index.ts: -------------------------------------------------------------------------------- 1 | import ExtrasServiceClientAdapter from "./ExtrasServiceClientAdapter"; 2 | import PlayerServiceClientSharedAdapter from "./PlayerServiceClientSharedAdapter"; 3 | import GameServiceClientAdapter from "./GameServiceClientAdapter"; 4 | import SetupServiceClientAdapter from "./SetupServiceClientAdapter"; 5 | 6 | export { 7 | ExtrasServiceClientAdapter, 8 | GameServiceClientAdapter, 9 | PlayerServiceClientSharedAdapter, 10 | SetupServiceClientAdapter 11 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/rsocket/ExtrasServiceClientAdapter.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveSocket } from "rsocket-types"; 2 | import { RSocketRPCServices } from "game-idl"; 3 | import ExtrasService from "../ExtrasService"; 4 | import { Extra } from "game-idl"; 5 | import { Flux } from "reactor-core-js/flux"; 6 | import { Empty } from "google-protobuf/google/protobuf/empty_pb"; 7 | import FlowableAdapter from "../FlowableAdapter"; 8 | import {IMeterRegistry} from "rsocket-rpc-metrics"; 9 | 10 | export default class ExtrasServiceClientAdapter implements ExtrasService { 11 | 12 | private service: RSocketRPCServices.ExtrasService; 13 | 14 | constructor(rSocket: ReactiveSocket, meterRegistry: IMeterRegistry) { 15 | this.service = new RSocketRPCServices.ExtrasServiceClient(rSocket, undefined, meterRegistry); 16 | } 17 | 18 | extras(): Flux { 19 | return Flux.from(FlowableAdapter.wrap(this.service.extras(new Empty()) as any)) 20 | .map(extra => extra.toObject()); 21 | } 22 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/rsocket/GameServiceClientAdapter.ts: -------------------------------------------------------------------------------- 1 | import GameService from "../GameService"; 2 | import { Nickname } from "game-idl"; 3 | import { Config } from "game-idl"; 4 | import { ReactiveSocket } from "rsocket-types"; 5 | import { RSocketRPCServices } from "game-idl"; 6 | import { Single } from "rsocket-flowable"; 7 | import {IMeterRegistry} from "rsocket-rpc-metrics"; 8 | 9 | export default class GameServiceClientAdapter implements GameService { 10 | 11 | private service: RSocketRPCServices.GameService; 12 | 13 | constructor(rSocket: ReactiveSocket, meterRegistry: IMeterRegistry) { 14 | this.service = new RSocketRPCServices.GameServiceClient(rSocket, undefined, meterRegistry); 15 | } 16 | 17 | start({ value }: Nickname.AsObject): Single { 18 | const nicknameProto = new Nickname(); 19 | 20 | nicknameProto.setValue(value); 21 | 22 | return this.service.start(nicknameProto) 23 | .map((c: Config) => c.toObject()); 24 | } 25 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/rsocket/PlayerServiceClientSharedAdapter.ts: -------------------------------------------------------------------------------- 1 | import { Flux, DirectProcessor } from "reactor-core-js/flux"; 2 | import { Single } from "rsocket-flowable"; 3 | import { Player } from "game-idl"; 4 | import { Location } from "game-idl"; 5 | import PlayerService from "../PlayerService"; 6 | import { ReactiveSocket } from "rsocket-types"; 7 | import { RSocketRPCServices } from "game-idl"; 8 | import { Point } from "game-idl"; 9 | import { Empty } from "google-protobuf/google/protobuf/empty_pb"; 10 | import { Disposable } from "reactor-core-js"; 11 | import FlowableAdapter from "../FlowableAdapter"; 12 | import {IMeterRegistry} from "rsocket-rpc-metrics"; 13 | 14 | export default class PlayerServiceClientSharedAdapter implements PlayerService { 15 | 16 | private service: RSocketRPCServices.PlayerService; 17 | private sharedPlayersStream: DirectProcessor; 18 | 19 | constructor(rSocket: ReactiveSocket, meterRegistry: IMeterRegistry) { 20 | this.service = new RSocketRPCServices.PlayerServiceClient(rSocket, undefined, meterRegistry); 21 | } 22 | 23 | locate(locationStream: Flux): Single { 24 | return new Single(subject => { 25 | let disposable: Disposable = { 26 | dispose: () => {} 27 | }; 28 | 29 | subject.onSubscribe(() => disposable.dispose()); 30 | 31 | disposable = locationStream 32 | .map(location => { 33 | const locationProto = new Location(); 34 | const positionProto = new Point(); 35 | 36 | positionProto.setX(location.position.x); 37 | positionProto.setY(location.position.y); 38 | locationProto.setPosition(positionProto); 39 | locationProto.setDirection(location.direction); 40 | 41 | return locationProto; 42 | }) 43 | .compose(flux => FlowableAdapter.wrap(this.service.locate(flux as any) as any)) 44 | .consume( 45 | () => {}, 46 | (e: Error) => subject.onError(e), 47 | () => subject.onComplete() 48 | ) 49 | }) 50 | } 51 | 52 | players(): Flux { 53 | if (!this.sharedPlayersStream) { 54 | this.sharedPlayersStream = new DirectProcessor(); 55 | Flux.from(FlowableAdapter.wrap(this.service.players(new Empty()) as any)) 56 | .map(player => player.toObject()) 57 | .subscribe(this.sharedPlayersStream); 58 | } 59 | 60 | return this.sharedPlayersStream; 61 | } 62 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/rsocket/index.ts: -------------------------------------------------------------------------------- 1 | import ExtrasServiceClientAdapter from "./ExtrasServiceClientAdapter"; 2 | import PlayerServiceClientSharedAdapter from "./PlayerServiceClientSharedAdapter"; 3 | import GameServiceClientAdapter from "./GameServiceClientAdapter"; 4 | 5 | export { 6 | ExtrasServiceClientAdapter, 7 | GameServiceClientAdapter, 8 | PlayerServiceClientSharedAdapter 9 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/socket.io/ExtrasServiceClientAdapter.ts: -------------------------------------------------------------------------------- 1 | import {Extra} from "game-idl"; 2 | import ExtrasService from "../ExtrasService"; 3 | import {DirectProcessor, Flux} from "reactor-core-js/flux"; 4 | import {IMeterRegistry} from "rsocket-rpc-metrics"; 5 | 6 | export default class ExtrasServiceClientAdapter implements ExtrasService { 7 | 8 | private readonly sharedExtrasStream: DirectProcessor; 9 | 10 | constructor(socket: SocketIOClient.Socket, meterRegistry: IMeterRegistry) { 11 | // this.service = new RSocketRPCServices.ExtrasServiceClient(rSocket, undefined, meterRegistry); 12 | this.sharedExtrasStream = new DirectProcessor(); 13 | socket.on("extras", (data: ArrayBuffer) => { 14 | if (data && data.byteLength) { 15 | this.sharedExtrasStream.onNext(Extra.deserializeBinary(new Uint8Array(data)).toObject()); 16 | } 17 | }) 18 | } 19 | 20 | extras(): Flux { 21 | return this.sharedExtrasStream; 22 | } 23 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/socket.io/GameServiceClientAdapter.ts: -------------------------------------------------------------------------------- 1 | import GameService from "../GameService"; 2 | import {Config, Nickname} from "game-idl"; 3 | import {Single} from "rsocket-flowable"; 4 | import {IMeterRegistry} from "rsocket-rpc-metrics"; 5 | 6 | export default class GameServiceClientAdapter implements GameService { 7 | 8 | constructor(private readonly socket: SocketIOClient.Socket, meterRegistry: IMeterRegistry) { 9 | // this.service = new RSocketRPCServices.GameServiceClient(rSocket, undefined, meterRegistry); 10 | } 11 | 12 | start({ value }: Nickname.AsObject): Single { 13 | const nicknameProto = new Nickname(); 14 | 15 | nicknameProto.setValue(value); 16 | 17 | return new Single((downstream) => { 18 | let cancelled = false; 19 | 20 | downstream.onSubscribe(() => { 21 | cancelled = true; 22 | }); 23 | 24 | if (!cancelled) { 25 | ((this.socket as any).binary(true) as SocketIOClient.Socket).emit("start", nicknameProto.serializeBinary(), (data: Buffer) =>{ 26 | downstream.onComplete(Config.deserializeBinary(data)); 27 | }) 28 | } 29 | }) 30 | .map((c: Config) => c.toObject()); 31 | } 32 | } -------------------------------------------------------------------------------- /game-client/src/main/typescript/api/socket.io/index.ts: -------------------------------------------------------------------------------- 1 | import ExtrasServiceClientAdapter from "./ExtrasServiceClientAdapter"; 2 | import PlayerServiceClientSharedAdapter from "./PlayerServiceClientSharedAdapter"; 3 | import GameServiceClientAdapter from "./GameServiceClientAdapter"; 4 | 5 | export { 6 | ExtrasServiceClientAdapter, 7 | GameServiceClientAdapter, 8 | PlayerServiceClientSharedAdapter 9 | } -------------------------------------------------------------------------------- /game-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build/", 4 | "noImplicitAny": true, 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "target": "es5", 8 | "downlevelIteration": true, 9 | "allowJs": false, 10 | "rootDir": "", 11 | "typeRoots": [ 12 | "node_modules/@types", 13 | "./src/@types/typescript" 14 | ], 15 | "lib": [ 16 | "es6", 17 | "scripthost", 18 | "dom" 19 | ] 20 | }, 21 | "include": [ 22 | "./src/main/**/*" 23 | ], 24 | "exclude": [ 25 | "./src/@types", 26 | "./node_modules", 27 | "./build" 28 | ] 29 | } -------------------------------------------------------------------------------- /game-client/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CompressionPlugin = require('compression-webpack-plugin'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const webpack = require('webpack'); 5 | const CopyPlugin = require('copy-webpack-plugin'); 6 | 7 | module.exports = { 8 | entry: './src/main/typescript/boot.ts', 9 | mode: 'production', 10 | devServer: { 11 | contentBase: path.join(__dirname, 'build'), 12 | compress: true, 13 | host: "0.0.0.0", 14 | port: 9010, 15 | hot: true 16 | }, 17 | plugins: [ 18 | new HtmlWebpackPlugin({ 19 | title: 'Netifi Pac-Man', 20 | template: path.join(__dirname, 'src/main/resources/public/index.html'), 21 | favicon: path.join(__dirname, 'src/main/resources/public/favicon.ico') 22 | }), 23 | new CopyPlugin([ 24 | { from: 'src/main/resources/public', to: './' }, 25 | ]), 26 | new CompressionPlugin(), 27 | new webpack.HotModuleReplacementPlugin() 28 | ], 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.tsx?$/, 33 | use: 'ts-loader', 34 | exclude: [ 35 | /node_modules/, 36 | 37 | ] 38 | }, 39 | { 40 | test: /\.(png|svg|jpg|gif)$/, 41 | use: [ 42 | 'file-loader' 43 | ] 44 | } 45 | ] 46 | }, 47 | externals: { 48 | grpc: 'grpc' 49 | }, 50 | resolve: { 51 | extensions: ['.tsx', '.ts', '.js'] 52 | }, 53 | output: { 54 | filename: 'bundle.js', 55 | path: path.resolve(__dirname, 'build') 56 | } 57 | }; -------------------------------------------------------------------------------- /game-idl/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.google.protobuf' 3 | } 4 | 5 | dependencies { 6 | implementation 'com.google.protobuf:protobuf-java' 7 | implementation 'com.google.protobuf:protobuf-java-util' 8 | implementation 'io.grpc:grpc-protobuf' 9 | implementation 'io.rsocket.rpc:rsocket-rpc-core' 10 | implementation "com.salesforce.servicelibs:reactor-grpc-stub" 11 | 12 | protobuf 'io.rsocket.rpc:rsocket-rpc-protobuf-idl' 13 | } 14 | 15 | sourceSets { 16 | main { 17 | proto { srcDir 'src/main/proto' } 18 | } 19 | } 20 | 21 | protobuf { 22 | generatedFilesBaseDir = "${projectDir}/src/generated" 23 | 24 | protoc { 25 | artifact = 'com.google.protobuf:protoc' 26 | } 27 | 28 | plugins { 29 | grpc { 30 | artifact = "io.grpc:protoc-gen-grpc-java" 31 | } 32 | reactorGRpc { 33 | artifact = "com.salesforce.servicelibs:reactor-grpc:0.10.0-RC1:jdk8@jar" 34 | } 35 | rsocketRpc { 36 | artifact = "io.rsocket.rpc:rsocket-rpc-protobuf" 37 | } 38 | } 39 | 40 | generateProtoTasks { 41 | ofSourceSet('main')*.plugins { 42 | grpc {} 43 | rsocketRpc {} 44 | reactorGRpc {} 45 | } 46 | } 47 | } 48 | 49 | idea { 50 | module { 51 | sourceDirs += file("src/main/proto") 52 | sourceDirs += file("src/generated/main/java") 53 | sourceDirs += file("src/generated/main/grpc") 54 | sourceDirs += file("src/generated/main/reactorGRpc") 55 | sourceDirs += file("src/generated/main/rsocketRpc") 56 | 57 | generatedSourceDirs += file('src/generated/main/java') 58 | generatedSourceDirs += file("src/generated/main/grpc") 59 | generatedSourceDirs += file("src/generated/main/reactorGRpc") 60 | generatedSourceDirs += file("src/generated/main/rsocketRpc") 61 | } 62 | } 63 | 64 | clean { 65 | delete protobuf.generatedFilesBaseDir 66 | } 67 | -------------------------------------------------------------------------------- /game-idl/src/@types/typescript/reactor-core-js/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'reactor-core-js/flux' { 2 | import { Disposable } from "reactor-core-js"; 3 | import { Publisher, Subscriber, Subscription } from "reactor-core-js/reactive-streams-spec"; 4 | 5 | export class Flux implements Publisher { 6 | subscribe(s: Subscriber): void; 7 | 8 | static from(stream: Publisher): Flux; 9 | 10 | map (mapper: (t: T) => V): Flux; 11 | doOnNext(callback: (t: T) => void): Flux; 12 | 13 | 14 | 15 | compose(transformer: (flux: Flux) => Publisher): Flux 16 | 17 | consume(): Disposable; 18 | consume(onNextCallback: (t: T) => void): Disposable; 19 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void): Disposable; 20 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void, onCompleteCallback: () => void): Disposable; 21 | } 22 | 23 | export class DirectProcessor extends Flux implements Subscriber { 24 | onSubscribe(s: Subscription): void; 25 | onNext(t: T): void; 26 | onError(t: Error): void; 27 | onComplete(): void; 28 | } 29 | // } 30 | 31 | // declare module 'reactor-core-js/mono' { 32 | 33 | export class Mono implements Publisher { 34 | subscribe(): void; 35 | map (mapper: (t: T) => V): Mono; 36 | consume(): Disposable; 37 | consume(onNextCallback: (t: T) => void): Disposable; 38 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void): Disposable; 39 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void, onCompleteCallback: () => void): Disposable; 40 | 41 | } 42 | } 43 | 44 | declare module 'reactor-core-js' { 45 | export interface Disposable { 46 | dispose(): void; 47 | } 48 | } 49 | 50 | declare module 'reactor-core-js/reactive-streams-spec' { 51 | export interface Subscription { 52 | request(n: number): void; 53 | cancel(): void; 54 | } 55 | 56 | export interface Subscriber { 57 | onSubscribe(s: Subscription): void; 58 | onNext(t: T): void; 59 | onError(t: Error): void; 60 | onComplete(): void; 61 | } 62 | 63 | export interface Publisher { 64 | subscribe(s: Subscriber): void; 65 | } 66 | 67 | export interface Processor extends Subscriber, Publisher { } 68 | } -------------------------------------------------------------------------------- /game-idl/src/@types/typescript/reactor-core-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "@types/reactor-core-js@*", 3 | "_id": "@types/reactor-core-js@4.16.0", 4 | "_inBundle": false, 5 | "_integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==", 6 | "_location": "/@types/reactor-core-js", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "@types/reactor-core-js@*", 12 | "name": "@types/reactor-core-js", 13 | "escapedName": "@types%2freactor-core-js", 14 | "scope": "@types", 15 | "rawSpec": "*", 16 | "saveSpec": null, 17 | "fetchSpec": "*" 18 | }, 19 | "_requiredBy": [ 20 | "/@types/express", 21 | "/@types/serve-static" 22 | ], 23 | "_resolved": "https://registry.npmjs.org/@types/reactor-core-js/-/reactor-core-js-4.16.0.tgz", 24 | "_shasum": "fdfe777594ddc1fe8eb8eccce52e261b496e43e7", 25 | "_spec": "@types/reactor-core-js@*", 26 | "_where": "/Users/Shadowgun/Documents/Workspace/Java/dinoman-io/node_modules/@types/express", 27 | "bugs": { 28 | "url": "https://github.com/DefinitelyTyped/DefinitelyTyped/issues" 29 | }, 30 | "bundleDependencies": false, 31 | "contributors": [ 32 | { 33 | "name": "Boris Yankov", 34 | "url": "https://github.com/borisyankov" 35 | }, 36 | { 37 | "name": "Michał Lytek", 38 | "url": "https://github.com/19majkel94" 39 | }, 40 | { 41 | "name": "Kacper Polak", 42 | "url": "https://github.com/kacepe" 43 | }, 44 | { 45 | "name": "Satana Charuwichitratana", 46 | "url": "https://github.com/micksatana" 47 | }, 48 | { 49 | "name": "Sami Jaber", 50 | "url": "https://github.com/samijaber" 51 | } 52 | ], 53 | "dependencies": { 54 | "@types/events": "*", 55 | "@types/node": "*", 56 | "@types/range-parser": "*" 57 | }, 58 | "deprecated": false, 59 | "description": "TypeScript definitions for Express", 60 | "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped#readme", 61 | "license": "MIT", 62 | "main": "", 63 | "name": "@types/reactor-core-js", 64 | "repository": { 65 | "type": "git", 66 | "url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git" 67 | }, 68 | "scripts": {}, 69 | "typeScriptVersion": "2.2", 70 | "typesPublisherContentHash": "8be76390251659a06700a35c7e64af43c3207cbd3c16a34cc7491535b5535aa8", 71 | "version": "4.16.0" 72 | } 73 | -------------------------------------------------------------------------------- /game-idl/src/@types/typescript/rsocket-rpc-core/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'rsocket-rpc-core' { 2 | import { PayloadSerializers } from "rsocket-core"; 3 | import { Encodable, DuplexConnection, Responder, ReactiveSocket, Payload } from "rsocket-types"; 4 | import { Single, Flowable } from "rsocket-flowable"; 5 | 6 | export type ClientConfig = { 7 | serializers?: PayloadSerializers, 8 | setup: { 9 | keepAlive: number, 10 | lifetime: number, 11 | metadata?: Encodable, 12 | }, 13 | transport: DuplexConnection, 14 | responder?: Responder, 15 | } 16 | 17 | export class RpcClient { 18 | constructor(config: ClientConfig); 19 | close(): void; 20 | connect(): Single>; 21 | } 22 | 23 | export class RequestHandlingRSocket implements Responder { 24 | 25 | addService(service: string, handler: Responder): void; 26 | 27 | fireAndForget(payload: Payload): void; 28 | 29 | requestResponse(payload: Payload,): Single> 30 | 31 | requestStream(payload: Payload,): Flowable>; 32 | 33 | requestChannel(payloads: Flowable>,): Flowable>; 34 | 35 | metadataPush(payload: Payload): Single; 36 | } 37 | } -------------------------------------------------------------------------------- /game-idl/src/main/proto/config.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman; 4 | 5 | import "player.proto"; 6 | import "score.proto"; 7 | 8 | option java_package = "org.coinen.pacman"; 9 | option java_multiple_files = true; 10 | 11 | message Config { 12 | repeated Player players = 1; 13 | repeated int32 extras = 2; 14 | Player player = 3; 15 | repeated Score scores = 4; 16 | } -------------------------------------------------------------------------------- /game-idl/src/main/proto/extra.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman; 4 | 5 | option java_package = "org.coinen.pacman"; 6 | option java_multiple_files = true; 7 | 8 | message Extra { 9 | int32 last = 1; 10 | int32 current = 2; 11 | } -------------------------------------------------------------------------------- /game-idl/src/main/proto/location.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman; 4 | 5 | import "point.proto"; 6 | 7 | option java_package = "org.coinen.pacman"; 8 | option java_multiple_files = true; 9 | 10 | message Location { 11 | Point position = 1; 12 | Direction direction = 2; 13 | } 14 | 15 | enum Direction { 16 | UP = 0; 17 | LEFT = 1; 18 | DOWN = 2; 19 | RIGHT = 3; 20 | } -------------------------------------------------------------------------------- /game-idl/src/main/proto/map.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman; 4 | 5 | import "tile.proto"; 6 | import "size.proto"; 7 | 8 | option java_package = "org.coinen.pacman"; 9 | option java_multiple_files = true; 10 | 11 | message Map { 12 | repeated Tile tiles = 1; 13 | Size size = 2; 14 | } -------------------------------------------------------------------------------- /game-idl/src/main/proto/player.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman; 4 | 5 | import "location.proto"; 6 | import "speed.proto"; 7 | 8 | option java_package = "org.coinen.pacman"; 9 | option java_multiple_files = true; 10 | 11 | message Nickname { 12 | string value = 1; 13 | } 14 | 15 | message Player { 16 | string uuid = 1; 17 | string nickname = 2; 18 | int32 score = 3; 19 | Type type = 4; 20 | int64 timestamp = 5; 21 | Location location = 6; 22 | Speed speed = 7; 23 | State state = 8; 24 | 25 | enum Type { 26 | PACMAN = 0; 27 | GHOST = 1; 28 | } 29 | 30 | enum State { 31 | CONNECTED = 0; 32 | ACTIVE = 1; 33 | DISCONNECTED = 2; 34 | } 35 | } -------------------------------------------------------------------------------- /game-idl/src/main/proto/point.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman; 4 | 5 | option java_package = "org.coinen.pacman"; 6 | option java_multiple_files = true; 7 | 8 | message Point { 9 | float x = 1; 10 | float y = 2; 11 | } -------------------------------------------------------------------------------- /game-idl/src/main/proto/score.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman; 4 | 5 | option java_package = "org.coinen.pacman"; 6 | option java_multiple_files = true; 7 | 8 | message Score { 9 | int32 score = 1; 10 | string username = 2; 11 | string uuid = 3; 12 | } -------------------------------------------------------------------------------- /game-idl/src/main/proto/service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman; 4 | 5 | import "google/protobuf/empty.proto"; 6 | import "rsocket/options.proto"; 7 | import "player.proto"; 8 | import "location.proto"; 9 | import "point.proto"; 10 | import "size.proto"; 11 | import "speed.proto"; 12 | import "tile.proto"; 13 | import "extra.proto"; 14 | import "map.proto"; 15 | import "config.proto"; 16 | import "score.proto"; 17 | 18 | option java_package = "org.coinen.pacman"; 19 | option java_multiple_files = true; 20 | 21 | service MapService { 22 | rpc setup(Map) returns (google.protobuf.Empty) { 23 | option (io.rsocket.rpc.options) = { 24 | fire_and_forget: true 25 | }; 26 | } 27 | } 28 | 29 | service SetupService { 30 | 31 | rpc get(google.protobuf.Empty) returns (Map) {} 32 | } 33 | 34 | service GameService { 35 | rpc start (Nickname) returns (Config) {} 36 | } 37 | 38 | service PlayerService { 39 | rpc locate(stream Location) returns (google.protobuf.Empty) {} 40 | 41 | rpc players(google.protobuf.Empty) returns (stream Player) {} 42 | } 43 | 44 | service LocationService { 45 | rpc locate(Location) returns (google.protobuf.Empty) {} 46 | } 47 | 48 | service ExtrasService { 49 | rpc extras(google.protobuf.Empty) returns (stream Extra) {} 50 | } 51 | 52 | service ScoreService { 53 | rpc score(Player) returns (stream Score) {} 54 | } -------------------------------------------------------------------------------- /game-idl/src/main/proto/size.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman; 4 | 5 | option java_package = "org.coinen.pacman"; 6 | option java_multiple_files = true; 7 | 8 | message Size { 9 | int32 width = 1; 10 | int32 height = 2; 11 | } -------------------------------------------------------------------------------- /game-idl/src/main/proto/speed.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman; 4 | 5 | option java_package = "org.coinen.pacman"; 6 | option java_multiple_files = true; 7 | 8 | message Speed { 9 | int64 timestamp = 1; 10 | int32 distance = 2; 11 | int32 count = 3; 12 | } -------------------------------------------------------------------------------- /game-idl/src/main/proto/tile.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman; 4 | 5 | import "point.proto"; 6 | 7 | option java_package = "org.coinen.pacman"; 8 | option java_multiple_files = true; 9 | 10 | message Tile { 11 | Point point = 1; 12 | repeated bool walls = 2; 13 | bool checked = 3; 14 | } -------------------------------------------------------------------------------- /game-idl/src/main/typescript/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../../generated/main/javascript/tile_pb'; 2 | export * from '../../generated/main/javascript/speed_pb'; 3 | export * from '../../generated/main/javascript/size_pb'; 4 | export * from '../../generated/main/javascript/score_pb'; 5 | export * from '../../generated/main/javascript/point_pb'; 6 | export * from '../../generated/main/javascript/player_pb'; 7 | export * from '../../generated/main/javascript/map_pb'; 8 | export * from '../../generated/main/javascript/location_pb'; 9 | export * from '../../generated/main/javascript/extra_pb'; 10 | export * from '../../generated/main/javascript/config_pb'; 11 | 12 | import * as RSocketRPCServices from '../../generated/main/javascript/service_rsocket_pb'; 13 | import * as GRPCWebServices from '../../generated/main/javascript/service_grpc_web_pb'; 14 | import * as GRPCServices from '../../generated/main/javascript/service_grpc_pb'; 15 | 16 | export { 17 | GRPCServices, 18 | GRPCWebServices, 19 | RSocketRPCServices 20 | } -------------------------------------------------------------------------------- /game-idl/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build", 4 | "noImplicitAny": true, 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "target": "es5", 8 | "downlevelIteration": true, 9 | "declaration": true, 10 | "typeRoots": [ 11 | "node_modules/@types", 12 | "src/@types/typescript" 13 | ], 14 | "lib": [ 15 | "es6", 16 | "scripthost", 17 | "dom" 18 | ], 19 | "baseUrl": "./", 20 | "rootDir": "./" 21 | }, 22 | "files": [ 23 | "src/main/typescript/index.ts" 24 | ], 25 | "include": [ 26 | "src/**/*" 27 | ], 28 | "exclude": [ 29 | "node_modules" 30 | ] 31 | } -------------------------------------------------------------------------------- /game-server/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.springframework.boot' 2 | apply plugin: 'com.bmuschko.docker-spring-boot-application' 3 | 4 | dependencies { 5 | 6 | implementation project(':game-idl') 7 | implementation project(':metrics-idl') 8 | implementation project(':metrics-client') 9 | implementation 'com.corundumstudio.socketio:netty-socketio' 10 | implementation 'io.socket:socket.io-client' 11 | implementation 'io.grpc:grpc-stub' 12 | implementation 'io.grpc:grpc-okhttp' 13 | implementation 'io.grpc:grpc-netty' 14 | implementation 'io.grpc:grpc-protobuf' 15 | implementation 'com.salesforce.servicelibs:reactor-grpc-stub' 16 | implementation 'org.roaringbitmap:RoaringBitmap' 17 | implementation 'org.jctools:jctools-core' 18 | implementation 'io.projectreactor.addons:reactor-extra' 19 | implementation 'io.rsocket:rsocket-core' 20 | implementation 'io.rsocket:rsocket-transport-netty' 21 | implementation "io.micrometer:micrometer-core" 22 | implementation 'io.rsocket.rpc:rsocket-rpc-core' 23 | implementation 'com.google.protobuf:protobuf-java' 24 | implementation 'io.github.lognet:grpc-spring-boot-starter' 25 | implementation 'com.github.collaborationinencapsulation.spring-boot-rsocket:spring-boot-starter-rsocket' 26 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 27 | implementation 'org.springframework.boot:spring-boot-starter-webflux' 28 | 29 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 30 | testImplementation 'io.projectreactor:reactor-test' 31 | } 32 | 33 | docker { 34 | springBootApplication { 35 | baseImage = 'adoptopenjdk/openjdk11' 36 | ports = [3000, 9090] 37 | mainClassName = 'org.coinen.reactive.pacman.ReactivePacManApplication' 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /game-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "game-server", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "npm run build", 8 | "build": "rm -rf src/main/typescript/dist ; tsc", 9 | "start": "node src/main/typescript/dist/index.js", 10 | "start:dev": "src/main/typescript/node_modules/.bin/webpack-dev-server" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@types/rsocket-types": "^0.0.1", 16 | "@types/rsocket-core": "^0.0.2", 17 | "@types/rsocket-flowable": "^0.0.2", 18 | "@types/rsocket-websocket-server": "^0.0.1", 19 | "@types/google-protobuf": "^3.7.1", 20 | "@types/uuid": "^3.4.5", 21 | "@types/socket.io": "^2.1.2", 22 | "body-parser": "^1.19.0", 23 | "socket.io": "^2.2.0", 24 | "game-idl": "0.0.1", 25 | "metrics-idl": "0.0.1", 26 | "metrics-client": "0.0.1", 27 | "cors": "^2.8.5", 28 | "express": "^4.17.1", 29 | "express-sse": "^0.5.1", 30 | "google-protobuf": "^3.9.0", 31 | "grpc": "^1.22.2", 32 | "reactor-core-js": "^0.5.0", 33 | "roaring": "^1.0.5", 34 | "rsocket-flowable": "^0.0.10", 35 | "rsocket-rpc-core": "^0.0.5-0", 36 | "rsocket-rpc-metrics": "^0.0.1", 37 | "rsocket-rpc-tracing": "^0.0.3", 38 | "rsocket-types": "^0.0.10", 39 | "rsocket-websocket-server": "0.0.10", 40 | "simplex-noise": "^2.4.0", 41 | "uuid": "^3.3.2" 42 | }, 43 | "devDependencies": { 44 | "@grpc/proto-loader": "^0.5.1", 45 | "@types/body-parser": "^1.17.0", 46 | "@types/cors": "^2.8.5", 47 | "@types/express": "^4.17.0", 48 | "async": "^2.6.2", 49 | "copy-webpack-plugin": "^5.0.1", 50 | "css-loader": "^2.1.0", 51 | "html-webpack-plugin": "^3.2.0", 52 | "livereload": "^0.8.0", 53 | "style-loader": "^0.23.1", 54 | "ts-loader": "^6.0.4", 55 | "ts-protoc-gen": "^0.10.0", 56 | "typescript": "^3.3.3333" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /game-server/src/main/@types/typescript/express-sse/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'express-sse' { 2 | import { Request, Response } from 'express'; 3 | import * as stream from "stream"; 4 | import * as http from "http"; 5 | export default class SSE { 6 | constructor(events?: any); 7 | public init(req: Request, res: Response): void; 8 | public send(event: any): void; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /game-server/src/main/@types/typescript/express/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import * as cookieParser from 'cookie-parser'; 3 | 4 | declare module 'express' { 5 | 6 | interface Request { 7 | body: any; 8 | cookies: cookieParser.CookieParseOptions 9 | uuid: string; 10 | } 11 | 12 | interface Response { 13 | send: Send 14 | } 15 | 16 | interface Send { 17 | (status: number, body?: any): Response; 18 | (body: any): Response; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /game-server/src/main/@types/typescript/reactor-core-js/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'reactor-core-js/flux' { 2 | import { Disposable } from "reactor-core-js"; 3 | import { Publisher, Subscriber, Subscription } from "reactor-core-js/reactive-streams-spec"; 4 | 5 | export class Flux implements Publisher { 6 | subscribe(s: Subscriber): void; 7 | 8 | static from(stream: Publisher): Flux; 9 | static just(t: T): Flux; 10 | 11 | map (mapper: (t: T) => V): Flux; 12 | doOnNext(callback: (t: T) => void): Flux; 13 | 14 | 15 | 16 | compose(transformer: (flux: Flux) => Publisher): Flux 17 | 18 | consume(): Disposable; 19 | consume(onNextCallback: (t: T) => void): Disposable; 20 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void): Disposable; 21 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void, onCompleteCallback: () => void): Disposable; 22 | } 23 | 24 | export class DirectProcessor extends Flux implements Subscriber { 25 | onSubscribe(s: Subscription): void; 26 | onNext(t: T): void; 27 | onError(t: Error): void; 28 | onComplete(): void; 29 | } 30 | // } 31 | 32 | // declare module 'reactor-core-js/mono' { 33 | 34 | export class Mono implements Publisher { 35 | subscribe(): void; 36 | map (mapper: (t: T) => V): Mono; 37 | consume(): Disposable; 38 | consume(onNextCallback: (t: T) => void): Disposable; 39 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void): Disposable; 40 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void, onCompleteCallback: () => void): Disposable; 41 | 42 | } 43 | } 44 | 45 | declare module 'reactor-core-js' { 46 | export interface Disposable { 47 | dispose(): void; 48 | } 49 | } 50 | 51 | declare module 'reactor-core-js/reactive-streams-spec' { 52 | export interface Subscription { 53 | request(n: number): void; 54 | cancel(): void; 55 | } 56 | 57 | export interface Subscriber { 58 | onSubscribe(s: Subscription): void; 59 | onNext(t: T): void; 60 | onError(t: Error): void; 61 | onComplete(): void; 62 | } 63 | 64 | export interface Publisher { 65 | subscribe(s: Subscriber): void; 66 | } 67 | 68 | export interface Processor extends Subscriber, Publisher { } 69 | } -------------------------------------------------------------------------------- /game-server/src/main/@types/typescript/reactor-core-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "@types/reactor-core-js@*", 3 | "_id": "@types/reactor-core-js@4.16.0", 4 | "_inBundle": false, 5 | "_integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==", 6 | "_location": "/@types/reactor-core-js", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "@types/reactor-core-js@*", 12 | "name": "@types/reactor-core-js", 13 | "escapedName": "@types%2freactor-core-js", 14 | "scope": "@types", 15 | "rawSpec": "*", 16 | "saveSpec": null, 17 | "fetchSpec": "*" 18 | }, 19 | "_requiredBy": [ 20 | "/@types/express", 21 | "/@types/serve-static" 22 | ], 23 | "_resolved": "https://registry.npmjs.org/@types/reactor-core-js/-/reactor-core-js-4.16.0.tgz", 24 | "_shasum": "fdfe777594ddc1fe8eb8eccce52e261b496e43e7", 25 | "_spec": "@types/reactor-core-js@*", 26 | "_where": "/Users/Shadowgun/Documents/Workspace/Java/dinoman-io/node_modules/@types/express", 27 | "bugs": { 28 | "url": "https://github.com/DefinitelyTyped/DefinitelyTyped/issues" 29 | }, 30 | "bundleDependencies": false, 31 | "contributors": [ 32 | { 33 | "name": "Boris Yankov", 34 | "url": "https://github.com/borisyankov" 35 | }, 36 | { 37 | "name": "Michał Lytek", 38 | "url": "https://github.com/19majkel94" 39 | }, 40 | { 41 | "name": "Kacper Polak", 42 | "url": "https://github.com/kacepe" 43 | }, 44 | { 45 | "name": "Satana Charuwichitratana", 46 | "url": "https://github.com/micksatana" 47 | }, 48 | { 49 | "name": "Sami Jaber", 50 | "url": "https://github.com/samijaber" 51 | } 52 | ], 53 | "dependencies": { 54 | "@types/events": "*", 55 | "@types/node": "*", 56 | "@types/range-parser": "*" 57 | }, 58 | "deprecated": false, 59 | "description": "TypeScript definitions for Express", 60 | "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped#readme", 61 | "license": "MIT", 62 | "main": "", 63 | "name": "@types/reactor-core-js", 64 | "repository": { 65 | "type": "git", 66 | "url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git" 67 | }, 68 | "scripts": {}, 69 | "typeScriptVersion": "2.2", 70 | "typesPublisherContentHash": "8be76390251659a06700a35c7e64af43c3207cbd3c16a34cc7491535b5535aa8", 71 | "version": "4.16.0" 72 | } 73 | -------------------------------------------------------------------------------- /game-server/src/main/@types/typescript/rsocket-rpc-core/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'rsocket-rpc-core' { 2 | import { PayloadSerializers } from "rsocket-core"; 3 | import { Encodable, DuplexConnection, Responder, ReactiveSocket, Payload } from "rsocket-types"; 4 | import { Single, Flowable } from "rsocket-flowable"; 5 | 6 | export type ClientConfig = { 7 | serializers?: PayloadSerializers, 8 | setup: { 9 | keepAlive: number, 10 | lifetime: number, 11 | metadata?: Encodable, 12 | }, 13 | transport: DuplexConnection, 14 | responder?: Responder, 15 | } 16 | 17 | export class RpcClient { 18 | constructor(config: ClientConfig); 19 | close(): void; 20 | connect(): Single>; 21 | } 22 | 23 | export class RequestHandlingRSocket implements Responder { 24 | 25 | addService(service: string, handler: Responder): void; 26 | 27 | fireAndForget(payload: Payload): void; 28 | 29 | requestResponse(payload: Payload,): Single> 30 | 31 | requestStream(payload: Payload,): Flowable>; 32 | 33 | requestChannel(payloads: Flowable>,): Flowable>; 34 | 35 | metadataPush(payload: Payload): Single; 36 | } 37 | } -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/ReactivePacManApplication.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ReactivePacManApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ReactivePacManApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/grpc/GrpcExtrasController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.grpc; 2 | 3 | import com.google.protobuf.Empty; 4 | import io.micrometer.core.instrument.MeterRegistry; 5 | import io.rsocket.rpc.metrics.Metrics; 6 | import org.coinen.pacman.Extra; 7 | import org.coinen.pacman.ReactorExtrasServiceGrpc; 8 | import org.coinen.reactive.pacman.service.ExtrasService; 9 | import org.lognet.springboot.grpc.GRpcService; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | import reactor.util.context.Context; 13 | 14 | import org.springframework.beans.factory.annotation.Qualifier; 15 | 16 | import static org.coinen.reactive.pacman.controller.grpc.config.UUIDInterceptor.CONTEXT_UUID_KEY; 17 | 18 | @GRpcService 19 | public class GrpcExtrasController extends ReactorExtrasServiceGrpc.ExtrasServiceImplBase { 20 | 21 | final ExtrasService extrasService; 22 | final MeterRegistry registry; 23 | 24 | public GrpcExtrasController(ExtrasService service, 25 | @Qualifier("grpc") MeterRegistry registry) { 26 | extrasService = service; 27 | this.registry = registry; 28 | } 29 | 30 | @Override 31 | public Flux extras(Mono request) { 32 | return extrasService.extras() 33 | .onBackpressureBuffer() 34 | .subscriberContext(Context.of("uuid", CONTEXT_UUID_KEY.get())); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/grpc/GrpcGameController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.grpc; 2 | 3 | import io.micrometer.core.instrument.MeterRegistry; 4 | import io.rsocket.rpc.metrics.Metrics; 5 | import org.coinen.pacman.Config; 6 | import org.coinen.pacman.Nickname; 7 | import org.coinen.pacman.ReactorGameServiceGrpc; 8 | import org.coinen.reactive.pacman.service.GameService; 9 | import org.lognet.springboot.grpc.GRpcService; 10 | import reactor.core.publisher.Mono; 11 | import reactor.util.context.Context; 12 | 13 | import org.springframework.beans.factory.annotation.Qualifier; 14 | 15 | import static org.coinen.reactive.pacman.controller.grpc.config.UUIDInterceptor.CONTEXT_UUID_KEY; 16 | 17 | @GRpcService 18 | public class GrpcGameController extends ReactorGameServiceGrpc.GameServiceImplBase { 19 | 20 | final GameService gameService; 21 | final MeterRegistry registry; 22 | 23 | public GrpcGameController(GameService gameService, 24 | @Qualifier("grpc") MeterRegistry registry) { 25 | this.gameService = gameService; 26 | this.registry = registry; 27 | } 28 | 29 | @Override 30 | public Mono start(Mono message) { 31 | return message.flatMap(gameService::start) 32 | .subscriberContext(Context.of("uuid", CONTEXT_UUID_KEY.get())); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/grpc/GrpcLocationServiceController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.grpc; 2 | 3 | import java.util.UUID; 4 | import java.util.concurrent.ConcurrentMap; 5 | 6 | import com.google.protobuf.Empty; 7 | import org.coinen.pacman.Location; 8 | import org.coinen.pacman.ReactorLocationServiceGrpc; 9 | import org.coinen.reactive.pacman.service.PlayerService; 10 | import org.jctools.maps.NonBlockingHashMap; 11 | import org.lognet.springboot.grpc.GRpcService; 12 | import reactor.core.publisher.Mono; 13 | import reactor.core.publisher.UnicastProcessor; 14 | import reactor.util.concurrent.Queues; 15 | import reactor.util.context.Context; 16 | 17 | import static org.coinen.reactive.pacman.controller.grpc.config.UUIDInterceptor.CONTEXT_UUID_KEY; 18 | 19 | @GRpcService 20 | public class GrpcLocationServiceController extends ReactorLocationServiceGrpc.LocationServiceImplBase { 21 | 22 | final PlayerService playerService; 23 | final ConcurrentMap> locationDirectProcessors; 24 | 25 | public GrpcLocationServiceController(PlayerService playerService) { 26 | this.playerService = playerService; 27 | this.locationDirectProcessors = new NonBlockingHashMap<>(); 28 | } 29 | 30 | @Override 31 | public Mono locate(Mono messages) { 32 | UUID uuid = CONTEXT_UUID_KEY.get(); 33 | 34 | return messages.map(location -> { 35 | UnicastProcessor processor = 36 | locationDirectProcessors.computeIfAbsent(uuid, __ -> { 37 | UnicastProcessor unicastProcessor = UnicastProcessor.create( 38 | Queues.unboundedMultiproducer().get(), 39 | () -> locationDirectProcessors.remove(uuid)); 40 | 41 | playerService.locate(unicastProcessor) 42 | .subscriberContext(Context.of("uuid", uuid)) 43 | .subscribe(); 44 | 45 | return unicastProcessor; 46 | }); 47 | 48 | processor.onNext(location); 49 | 50 | return Empty.getDefaultInstance(); 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/grpc/GrpcMetricsSnapshotHandlerProxyController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.grpc; 2 | 3 | import com.google.protobuf.Empty; 4 | import io.netty.buffer.ByteBuf; 5 | import org.coinen.pacman.metrics.MetricsSnapshot; 6 | import org.coinen.pacman.metrics.MetricsSnapshotHandler; 7 | import org.coinen.pacman.metrics.MetricsSnapshotHandlerClient; 8 | import org.coinen.pacman.metrics.ReactorMetricsSnapshotHandlerGrpc; 9 | import org.lognet.springboot.grpc.GRpcService; 10 | import org.reactivestreams.Publisher; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import reactor.core.publisher.Flux; 14 | import reactor.core.publisher.Mono; 15 | 16 | @GRpcService 17 | public class GrpcMetricsSnapshotHandlerProxyController extends 18 | ReactorMetricsSnapshotHandlerGrpc.MetricsSnapshotHandlerImplBase { 19 | static final Logger LOGGER = LoggerFactory.getLogger( 20 | GrpcMetricsSnapshotHandlerProxyController.class); 21 | 22 | final ReactorMetricsSnapshotHandlerGrpc.ReactorMetricsSnapshotHandlerStub delegate; 23 | 24 | public GrpcMetricsSnapshotHandlerProxyController(ReactorMetricsSnapshotHandlerGrpc.ReactorMetricsSnapshotHandlerStub delegate) { 25 | this.delegate = delegate; 26 | } 27 | 28 | @Override 29 | public Mono streamMetricsSnapshots(Flux messages) { 30 | return delegate.streamMetricsSnapshots(messages.hide()); 31 | } 32 | 33 | @Override 34 | public Mono sendMetricsSnapshot(Mono messageMono) { 35 | return delegate.sendMetricsSnapshot(messageMono); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/grpc/GrpcPlayerController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.grpc; 2 | 3 | import com.google.protobuf.Empty; 4 | import io.micrometer.core.instrument.MeterRegistry; 5 | import io.rsocket.rpc.metrics.Metrics; 6 | import org.coinen.pacman.Player; 7 | import org.coinen.pacman.ReactorPlayerServiceGrpc; 8 | import org.coinen.reactive.pacman.service.PlayerService; 9 | import org.lognet.springboot.grpc.GRpcService; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | import reactor.util.context.Context; 13 | 14 | import org.springframework.beans.factory.annotation.Qualifier; 15 | 16 | import static org.coinen.reactive.pacman.controller.grpc.config.UUIDInterceptor.CONTEXT_UUID_KEY; 17 | 18 | @GRpcService 19 | public class GrpcPlayerController extends ReactorPlayerServiceGrpc.PlayerServiceImplBase { 20 | 21 | final PlayerService playerService; 22 | final MeterRegistry registry; 23 | 24 | public GrpcPlayerController(PlayerService playerService, 25 | @Qualifier("grpc") MeterRegistry registry) { 26 | this.playerService = playerService; 27 | this.registry = registry; 28 | } 29 | 30 | @Override 31 | public Flux players(Mono message) { 32 | return playerService 33 | .players() 34 | .onBackpressureBuffer() 35 | .subscriberContext(Context.of("uuid", CONTEXT_UUID_KEY.get())); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/grpc/GrpcSetupController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.grpc; 2 | 3 | import com.google.protobuf.Empty; 4 | import io.micrometer.core.instrument.MeterRegistry; 5 | import io.rsocket.rpc.metrics.Metrics; 6 | import org.coinen.pacman.Map; 7 | import org.coinen.pacman.ReactorSetupServiceGrpc; 8 | import org.coinen.reactive.pacman.service.MapService; 9 | import org.lognet.springboot.grpc.GRpcService; 10 | import reactor.core.publisher.Mono; 11 | 12 | import org.springframework.beans.factory.annotation.Qualifier; 13 | 14 | @GRpcService 15 | public class GrpcSetupController extends ReactorSetupServiceGrpc.SetupServiceImplBase { 16 | 17 | final MapService mapService; 18 | final MeterRegistry registry; 19 | 20 | public GrpcSetupController(MapService mapService, 21 | @Qualifier("grpc") MeterRegistry registry) { 22 | this.mapService = mapService; 23 | this.registry = registry; 24 | } 25 | 26 | @Override 27 | public Mono get(Mono request) { 28 | return Mono.just(mapService.getMap()); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/grpc/config/UUIDInterceptor.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.grpc.config; 2 | 3 | import java.util.UUID; 4 | 5 | import io.grpc.Context; 6 | import io.grpc.Contexts; 7 | import io.grpc.Metadata; 8 | import io.grpc.ServerCall; 9 | import io.grpc.ServerCallHandler; 10 | import io.grpc.ServerInterceptor; 11 | import org.lognet.springboot.grpc.GRpcGlobalInterceptor; 12 | 13 | import static io.grpc.Metadata.ASCII_STRING_MARSHALLER; 14 | 15 | public class UUIDInterceptor implements ServerInterceptor { 16 | 17 | private static final Metadata.Key META_UUID_KEY = Metadata.Key.of("uuid", ASCII_STRING_MARSHALLER); 18 | 19 | public static final Context.Key CONTEXT_UUID_KEY = Context.key("uuid"); 20 | 21 | @Override 22 | public ServerCall.Listener interceptCall( 23 | ServerCall call, 24 | Metadata headers, 25 | ServerCallHandler next 26 | ) { 27 | String uuid = headers.get(META_UUID_KEY); 28 | Context context = Context.current(); 29 | 30 | if (uuid != null) { 31 | context = context.withValue(CONTEXT_UUID_KEY, UUID.fromString(uuid)); 32 | } 33 | 34 | return Contexts.interceptCall(context, call,headers, next); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/http/HttpExtrasController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.http; 2 | 3 | import java.util.Arrays; 4 | 5 | import io.micrometer.core.instrument.MeterRegistry; 6 | import io.rsocket.rpc.metrics.Metrics; 7 | import org.coinen.reactive.pacman.service.ExtrasService; 8 | import reactor.core.publisher.Flux; 9 | import reactor.util.context.Context; 10 | 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.web.bind.annotation.CookieValue; 13 | import org.springframework.web.bind.annotation.CrossOrigin; 14 | import org.springframework.web.bind.annotation.GetMapping; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | @RestController 20 | @RequestMapping("/http") 21 | public class HttpExtrasController { 22 | final ExtrasService extrasService; 23 | final MeterRegistry registry; 24 | 25 | public HttpExtrasController(ExtrasService service, 26 | @Qualifier("http") MeterRegistry registry) { 27 | extrasService = service; 28 | this.registry = registry; 29 | } 30 | 31 | @GetMapping("/extras") 32 | @CrossOrigin(origins = "*", methods = RequestMethod.GET, allowedHeaders = "*", allowCredentials = "true") 33 | public Flux extras(@CookieValue("uuid") String uuid) { 34 | return extrasService.extras() 35 | .map(e -> Arrays.toString(e.toByteArray())) 36 | .onBackpressureBuffer() 37 | .transform(Metrics.timed(registry, "http.server", "service", org.coinen.pacman.ExtrasService.SERVICE, "method", org.coinen.pacman.ExtrasService.METHOD_EXTRAS)) 38 | .subscriberContext(Context.of("uuid", uuid)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/http/HttpGameController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.http; 2 | 3 | import java.util.UUID; 4 | 5 | import io.micrometer.core.instrument.MeterRegistry; 6 | import io.rsocket.rpc.metrics.Metrics; 7 | import org.coinen.pacman.Config; 8 | import org.coinen.pacman.Nickname; 9 | import org.coinen.reactive.pacman.service.GameService; 10 | import reactor.core.publisher.Mono; 11 | import reactor.util.context.Context; 12 | 13 | import org.springframework.beans.factory.annotation.Qualifier; 14 | import org.springframework.web.bind.annotation.CookieValue; 15 | import org.springframework.web.bind.annotation.CrossOrigin; 16 | import org.springframework.web.bind.annotation.PostMapping; 17 | import org.springframework.web.bind.annotation.RequestBody; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RequestMethod; 20 | import org.springframework.web.bind.annotation.RestController; 21 | 22 | @RestController 23 | @RequestMapping("/http") 24 | public class HttpGameController { 25 | 26 | final GameService gameService; 27 | final MeterRegistry registry; 28 | 29 | public HttpGameController(GameService gameService, 30 | @Qualifier("http") MeterRegistry registry) { 31 | this.gameService = gameService; 32 | this.registry = registry; 33 | } 34 | 35 | @PostMapping("/start") 36 | @CrossOrigin(origins = "*", methods = RequestMethod.POST, allowedHeaders = "*", 37 | allowCredentials = "true") 38 | public Mono start(@RequestBody Nickname nickname, @CookieValue("uuid") String uuid) { 39 | return gameService.start(nickname) 40 | .transform(Metrics.timed(registry, "http.server", "service", org.coinen.pacman.GameService.SERVICE, "method", org.coinen.pacman.GameService.METHOD_START)) 41 | .subscriberContext(Context.of("uuid", UUID.fromString(uuid))); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/http/HttpSetupController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.http; 2 | 3 | import java.time.Clock; 4 | import java.util.UUID; 5 | import java.util.concurrent.ThreadLocalRandom; 6 | 7 | import org.coinen.pacman.Map; 8 | import org.coinen.reactive.pacman.service.MapService; 9 | 10 | import org.springframework.http.ResponseCookie; 11 | import org.springframework.web.bind.annotation.CrossOrigin; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestMethod; 15 | import org.springframework.web.bind.annotation.RestController; 16 | import org.springframework.web.server.ServerWebExchange; 17 | 18 | @RestController 19 | @RequestMapping("/http") 20 | public class HttpSetupController { 21 | 22 | final MapService mapService; 23 | 24 | public HttpSetupController(MapService service) { 25 | mapService = service; 26 | } 27 | 28 | @GetMapping("/setup") 29 | @CrossOrigin(origins = "*", methods = RequestMethod.GET, allowedHeaders = "*", allowCredentials = "true") 30 | public Map setup(ServerWebExchange webExchange) { 31 | UUID uuid = new UUID(Clock.systemUTC().millis(), ThreadLocalRandom.current().nextLong()); 32 | 33 | webExchange.getResponse().addCookie(ResponseCookie.from("uuid", uuid.toString()).build()); 34 | 35 | return mapService.getMap(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/http/config/HttpGameServerConfig.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.http.config; 2 | 3 | import io.micrometer.core.instrument.MeterRegistry; 4 | import org.coinen.reactive.pacman.metrics.ReactiveMetricsRegistry; 5 | 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class HttpGameServerConfig { 13 | 14 | @Value("${http.metrics-endpoint}") 15 | String url; 16 | 17 | @Bean 18 | @Qualifier("http") 19 | public MeterRegistry reactiveHttpMeterRegistry() { 20 | // WebClient client = WebClient.create(url); 21 | ReactiveMetricsRegistry registry = new ReactiveMetricsRegistry("http.game.server"); 22 | // registry.asFlux() 23 | // .retryWhen( 24 | // Retry.any() 25 | // .exponentialBackoffWithJitter(Duration.ofSeconds(1), Duration.ofMinutes(1)) 26 | // .retryMax(Long.MAX_VALUE) 27 | // ) 28 | // .subscribe(ms -> Mono.defer(() -> client.post() 29 | // .uri("/http/metrics") 30 | // .syncBody(ms) 31 | // .exchange() 32 | // .flatMap(cr -> Mono.empty()) 33 | // ) 34 | // .onErrorResume(__ -> Mono.empty()) 35 | // .subscribe()); 36 | 37 | return registry; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/rsocket/ExtrasController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.rsocket; 2 | 3 | import com.google.protobuf.Empty; 4 | import io.netty.buffer.ByteBuf; 5 | import org.coinen.pacman.Extra; 6 | import org.coinen.reactive.pacman.service.ExtrasService; 7 | import reactor.core.publisher.Flux; 8 | 9 | public class ExtrasController implements org.coinen.pacman.ExtrasService { 10 | final ExtrasService extrasService; 11 | 12 | public ExtrasController(ExtrasService service) { 13 | extrasService = service; 14 | } 15 | 16 | @Override 17 | public Flux extras(Empty message, ByteBuf metadata) { 18 | return extrasService.extras(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/rsocket/GameController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.rsocket; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.coinen.pacman.Config; 5 | import org.coinen.pacman.Nickname; 6 | import org.coinen.reactive.pacman.service.GameService; 7 | import reactor.core.publisher.Mono; 8 | 9 | public class GameController implements org.coinen.pacman.GameService { 10 | 11 | final GameService gameService; 12 | 13 | public GameController(GameService gameService) { 14 | this.gameService = gameService; 15 | } 16 | 17 | @Override 18 | public Mono start(Nickname message, ByteBuf metadata) { 19 | return gameService.start(message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/rsocket/MetricsSnapshotHandlerProxyController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.rsocket; 2 | 3 | import com.google.protobuf.Empty; 4 | import io.netty.buffer.ByteBuf; 5 | import org.coinen.pacman.metrics.MetricsSnapshot; 6 | import org.coinen.pacman.metrics.MetricsSnapshotHandler; 7 | import org.coinen.pacman.metrics.MetricsSnapshotHandlerClient; 8 | import org.reactivestreams.Publisher; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import reactor.core.publisher.Mono; 12 | 13 | public class MetricsSnapshotHandlerProxyController implements MetricsSnapshotHandler { 14 | static final Logger LOGGER = LoggerFactory.getLogger(MetricsSnapshotHandlerProxyController.class); 15 | 16 | final MetricsSnapshotHandlerClient delegate; 17 | 18 | public MetricsSnapshotHandlerProxyController(MetricsSnapshotHandlerClient delegate) { 19 | this.delegate = delegate; 20 | } 21 | 22 | @Override 23 | public Mono streamMetricsSnapshots(Publisher messages, ByteBuf metadata) { 24 | return delegate.streamMetricsSnapshots(messages, metadata.retain()); 25 | } 26 | 27 | @Override 28 | public Mono sendMetricsSnapshot(MetricsSnapshot message, ByteBuf metadata) { 29 | return delegate.sendMetricsSnapshot(message, metadata.retain()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/rsocket/PlayerController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.rsocket; 2 | 3 | import com.google.protobuf.Empty; 4 | import io.netty.buffer.ByteBuf; 5 | import org.coinen.pacman.Location; 6 | import org.coinen.pacman.Player; 7 | import org.coinen.reactive.pacman.service.PlayerService; 8 | import org.reactivestreams.Publisher; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | public class PlayerController implements org.coinen.pacman.PlayerService { 13 | 14 | final PlayerService playerService; 15 | 16 | public PlayerController(PlayerService playerService) { 17 | this.playerService = playerService; 18 | } 19 | 20 | @Override 21 | public Mono locate(Publisher messages, ByteBuf metadata) { 22 | return playerService.locate(Flux.from(messages)) 23 | .thenReturn(Empty.getDefaultInstance()); 24 | } 25 | 26 | @Override 27 | public Flux players(Empty message, ByteBuf metadata) { 28 | return playerService.players(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/rsocket/SetupController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.rsocket; 2 | 3 | import java.time.Clock; 4 | import java.util.UUID; 5 | import java.util.concurrent.ThreadLocalRandom; 6 | import java.util.function.Supplier; 7 | 8 | import io.rsocket.ConnectionSetupPayload; 9 | import io.rsocket.RSocket; 10 | import io.rsocket.RSocketFactory; 11 | import io.rsocket.SocketAcceptor; 12 | import io.rsocket.rpc.rsocket.RequestHandlingRSocket; 13 | import org.coinen.pacman.MapServiceClient; 14 | import org.coinen.reactive.pacman.controller.rsocket.support.UuidAwareRSocket; 15 | import org.coinen.reactive.pacman.service.MapService; 16 | import org.coinen.reactive.pacman.service.PlayerService; 17 | import reactor.core.publisher.Mono; 18 | import reactor.util.context.Context; 19 | 20 | public class SetupController implements SocketAcceptor { 21 | 22 | final Supplier serverRSocketSupplier; 23 | final MapService mapService; 24 | final PlayerService playerService; 25 | 26 | public SetupController(Supplier requestHandlingRSocketSupplier, 27 | MapService service, 28 | PlayerService playerService) { 29 | serverRSocketSupplier = requestHandlingRSocketSupplier; 30 | mapService = service; 31 | this.playerService = playerService; 32 | } 33 | 34 | @Override 35 | public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { 36 | final UUID uuid = new UUID(Clock.systemUTC().millis(), ThreadLocalRandom.current().nextLong()); 37 | 38 | sendingSocket.onClose() 39 | .onErrorResume(e -> Mono.empty()) 40 | .then(playerService.disconnectPlayer()) 41 | .subscriberContext(Context.of("uuid", uuid)) 42 | .subscribe(); 43 | 44 | return Mono.just(new UuidAwareRSocket(serverRSocketSupplier.get(), uuid)) 45 | .mergeWith(new MapServiceClient(sendingSocket).setup(mapService.getMap()) 46 | .then(Mono.empty())) 47 | .subscriberContext(Context.of("uuid", uuid)) 48 | .singleOrEmpty(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/controller/rsocket/support/UuidAwareRSocket.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.controller.rsocket.support; 2 | 3 | import java.util.UUID; 4 | 5 | import io.rsocket.Payload; 6 | import io.rsocket.RSocket; 7 | import io.rsocket.ResponderRSocket; 8 | import io.rsocket.util.RSocketProxy; 9 | import org.reactivestreams.Publisher; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | import reactor.util.context.Context; 13 | 14 | public class UuidAwareRSocket extends RSocketProxy implements ResponderRSocket { 15 | 16 | private final UUID uuid; 17 | 18 | public UuidAwareRSocket(RSocket source, UUID uuid) { 19 | super(source); 20 | this.uuid = uuid; 21 | } 22 | 23 | @Override 24 | public Mono fireAndForget(Payload payload) { 25 | return super.fireAndForget(payload).subscriberContext(Context.of("uuid", uuid)); 26 | } 27 | 28 | @Override 29 | public Mono requestResponse(Payload payload) { 30 | return super.requestResponse(payload).subscriberContext(Context.of("uuid", uuid)); 31 | } 32 | 33 | @Override 34 | public Flux requestStream(Payload payload) { 35 | return super.requestStream(payload).subscriberContext(Context.of("uuid", uuid)); 36 | } 37 | 38 | @Override 39 | public Flux requestChannel(Publisher payloads) { 40 | return super.requestChannel(payloads).subscriberContext(Context.of("uuid", uuid)); 41 | } 42 | 43 | @Override 44 | public Mono metadataPush(Payload payload) { 45 | return super.metadataPush(payload).subscriberContext(Context.of("uuid", uuid)); 46 | } 47 | 48 | @Override 49 | public Flux requestChannel(Payload payload, Publisher payloads) { 50 | if (source instanceof ResponderRSocket) { 51 | return ((ResponderRSocket) source).requestChannel(payload, payloads) 52 | .subscriberContext(Context.of("uuid", uuid)); 53 | } else { 54 | return requestChannel( 55 | Flux.from(payloads) 56 | .skip(1) 57 | .startWith(payload) 58 | ); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/repository/ExtrasRepository.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.repository; 2 | 3 | public interface ExtrasRepository { 4 | 5 | int collideExtra(float x, float y); 6 | 7 | int createExtra(int size); 8 | 9 | Iterable finaAll(); 10 | 11 | default int[] generate(int width, int height, int offset) { 12 | var iterations = 13 | (int) ((width - 2 * offset) * (height - 2 * offset) * (0.3 + Math.random() * 0.3)); 14 | var extras = new int[iterations]; 15 | 16 | for (var i = 0; i < iterations; i++) { 17 | extras[i] = randomPosition(width, height, offset); 18 | } 19 | 20 | return extras; 21 | } 22 | 23 | static int randomPosition(int width, int height, int offset) { 24 | return (int) (Math.floor(Math.random() * (width - 2 * offset + 1) + offset) + Math.floor(Math.random() * (height - 2 * offset + 1) + offset) * height); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/repository/PlayerRepository.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.repository; 2 | 3 | import java.util.Collection; 4 | import java.util.UUID; 5 | import java.util.function.Function; 6 | import java.util.function.Supplier; 7 | 8 | import org.coinen.pacman.Player; 9 | 10 | public interface PlayerRepository { 11 | 12 | Collection findAll(); 13 | 14 | Player findOne(UUID uuid); 15 | 16 | Player update(UUID uuid, Function playerUpdater); 17 | 18 | Player save(UUID uuid, Supplier playerSupplier); 19 | 20 | Player delete(UUID uuid); 21 | 22 | int count(); 23 | } 24 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/repository/PowerRepository.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.repository; 2 | 3 | public interface PowerRepository { 4 | 5 | boolean isPowerUp(); 6 | 7 | void powerUp(); 8 | } 9 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/repository/support/InMemoryPlayerRepository.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.repository.support; 2 | 3 | import java.util.Collection; 4 | import java.util.UUID; 5 | import java.util.concurrent.ConcurrentMap; 6 | import java.util.function.Function; 7 | import java.util.function.Supplier; 8 | 9 | import org.coinen.pacman.Player; 10 | import org.coinen.reactive.pacman.repository.PlayerRepository; 11 | import org.jctools.maps.NonBlockingHashMap; 12 | 13 | public class InMemoryPlayerRepository implements PlayerRepository { 14 | 15 | final ConcurrentMap store = new NonBlockingHashMap<>(); 16 | 17 | @Override 18 | public Collection findAll() { 19 | return store.values(); 20 | } 21 | 22 | @Override 23 | public Player findOne(UUID uuid) { 24 | return store.get(uuid); 25 | } 26 | 27 | @Override 28 | public Player update(UUID uuid, Function playerUpdater) { 29 | return store.compute(uuid, (__, player) -> playerUpdater.apply(player)); 30 | } 31 | 32 | @Override 33 | public Player save(UUID uuid, Supplier playerSupplier) { 34 | return store.computeIfAbsent(uuid, __ -> playerSupplier.get()); 35 | } 36 | 37 | @Override 38 | public Player delete(UUID uuid) { 39 | return store.remove(uuid); 40 | } 41 | 42 | @Override 43 | public int count() { 44 | return store.size(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/repository/support/InMemoryPowerRepository.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.repository.support; 2 | 3 | import java.time.Duration; 4 | import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; 5 | 6 | import org.coinen.reactive.pacman.repository.PowerRepository; 7 | import reactor.core.publisher.Mono; 8 | 9 | public class InMemoryPowerRepository implements PowerRepository { 10 | volatile int powerUpCounter; 11 | final static AtomicIntegerFieldUpdater POWER_UP_COUNTER = 12 | AtomicIntegerFieldUpdater.newUpdater(InMemoryPowerRepository.class, "powerUpCounter"); 13 | 14 | @Override 15 | public boolean isPowerUp() { 16 | return powerUpCounter > 0; 17 | } 18 | 19 | @Override 20 | public void powerUp() { 21 | POWER_UP_COUNTER.incrementAndGet(this); 22 | Mono.delay(Duration.ofSeconds(10)) 23 | .subscribe(__ -> POWER_UP_COUNTER.decrementAndGet(this)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/service/ExtrasService.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.service; 2 | 3 | import org.coinen.pacman.Extra; 4 | import reactor.core.publisher.Flux; 5 | 6 | public interface ExtrasService { 7 | 8 | Flux extras(); 9 | 10 | int check(float x, float y); 11 | } 12 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/service/GameService.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.service; 2 | 3 | import org.coinen.pacman.Config; 4 | import org.coinen.pacman.Nickname; 5 | import reactor.core.publisher.Mono; 6 | 7 | public interface GameService { 8 | 9 | Mono start(Nickname nickname); 10 | } 11 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/service/MapService.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.service; 2 | 3 | import java.util.List; 4 | 5 | import org.coinen.pacman.Map; 6 | import org.coinen.pacman.Point; 7 | 8 | public interface MapService { 9 | 10 | Map getMap(); 11 | 12 | List getTilesPositions(); 13 | 14 | Point getRandomPoint(); 15 | } 16 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/service/PlayerService.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.service; 2 | 3 | import org.coinen.pacman.Location; 4 | import org.coinen.pacman.Player; 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | 8 | public interface PlayerService { 9 | 10 | Mono createPlayer(String nickname); 11 | 12 | Mono disconnectPlayer(); 13 | 14 | Mono locate(Flux locationStream); 15 | 16 | Flux players(); 17 | } 18 | -------------------------------------------------------------------------------- /game-server/src/main/java/org/coinen/reactive/pacman/service/support/DefaultExtrasService.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.service.support; 2 | 3 | import org.coinen.pacman.Extra; 4 | import org.coinen.reactive.pacman.repository.ExtrasRepository; 5 | import org.coinen.reactive.pacman.repository.PlayerRepository; 6 | import org.coinen.reactive.pacman.repository.PowerRepository; 7 | import org.coinen.reactive.pacman.service.ExtrasService; 8 | import reactor.core.publisher.DirectProcessor; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.FluxSink; 11 | 12 | public class DefaultExtrasService implements ExtrasService { 13 | final ExtrasRepository extrasRepository; 14 | final PlayerRepository playerRepository; 15 | final PowerRepository powerRepository; 16 | final DirectProcessor extrasProcessor = DirectProcessor.create(); 17 | final FluxSink extrasFluxSink = extrasProcessor.sink(); 18 | 19 | 20 | public DefaultExtrasService( 21 | ExtrasRepository extrasRepository, 22 | PlayerRepository playerRepository, 23 | PowerRepository powerRepository 24 | ) { 25 | this.extrasRepository = extrasRepository; 26 | this.playerRepository = playerRepository; 27 | this.powerRepository = powerRepository; 28 | } 29 | 30 | @Override 31 | public Flux extras() { 32 | return extrasProcessor; 33 | } 34 | 35 | @Override 36 | public int check(float x, float y) { 37 | var retainedExtra = extrasRepository.collideExtra(x, y); 38 | 39 | if (retainedExtra != 0) { 40 | if (Math.signum(retainedExtra) == -1.0f) { 41 | powerRepository.powerUp(); 42 | } 43 | 44 | var addedExtra = extrasRepository.createExtra(playerRepository.count()); 45 | 46 | var extra = Extra.newBuilder() 47 | .setLast(retainedExtra) 48 | .setCurrent(addedExtra) 49 | .build(); 50 | 51 | extrasFluxSink.next(extra); 52 | 53 | return retainedExtra; 54 | } 55 | 56 | return 0; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /game-server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | grpc.metrics-endpoint=ws://localhost:9095 2 | socket.io.metrics-endpoint=http://localhost:6900 3 | grpc.port=9090 4 | http.metrics-endpoint=http://localhost:4000 5 | rsocket.metrics-endpoint=ws://localhost:4000 6 | rsocket.server.path=/ 7 | server.port=3000 8 | #server.address=10.5.50.167 9 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/grpc/ExtrasController.ts: -------------------------------------------------------------------------------- 1 | import { ExtrasService } from "../../service"; 2 | import { GRPCServices, Extra } from "game-idl"; 3 | import { ServerWriteableStream } from "grpc"; 4 | 5 | export class GrpcExtrasController implements GRPCServices.IExtrasServiceServer { 6 | constructor(private extrasService: ExtrasService) {} 7 | 8 | extras(call: ServerWriteableStream) { 9 | this.extrasService.extras() 10 | .consume((extra) => call.write(extra), e => call.end(), () => call.end()); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/grpc/GameController.ts: -------------------------------------------------------------------------------- 1 | import { GameService } from "../../service"; 2 | import { ServerUnaryCall, sendUnaryData } from "grpc"; 3 | import { Nickname, Config } from "game-idl"; 4 | import { GRPCServices } from "game-idl"; 5 | 6 | export class GrpcGameController implements GRPCServices.IGameServiceServer { 7 | constructor(private gameService: GameService) {} 8 | start(call: ServerUnaryCall, callback: sendUnaryData) { 9 | callback(null, this.gameService.start(call.metadata.get('uuid')[0] as string, call.request)); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/grpc/LocationController.ts: -------------------------------------------------------------------------------- 1 | import {PlayerService} from "../../service"; 2 | import {Empty} from "google-protobuf/google/protobuf/empty_pb"; 3 | import {GRPCServices, Location} from 'game-idl' 4 | import {sendUnaryData, ServerUnaryCall} from "grpc"; 5 | 6 | export class GrpcLocationController implements GRPCServices.ILocationServiceServer { 7 | constructor(private playerService: PlayerService) { 8 | } 9 | locate(call: ServerUnaryCall, callback: sendUnaryData): void { 10 | this.playerService.locate(call.metadata.get('uuid')[0] as string, call.request); 11 | callback(null, new Empty()); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/grpc/PlayerController.ts: -------------------------------------------------------------------------------- 1 | import { PlayerService } from "../../service"; 2 | import { ServerUnaryCall, sendUnaryData, ServerWriteableStream, ServerReadableStream } from "grpc"; 3 | import { Player } from "game-idl"; 4 | import { GRPCServices } from "game-idl"; 5 | import { Empty } from "google-protobuf/google/protobuf/empty_pb"; 6 | 7 | export class GrpcPlayerController implements GRPCServices.IPlayerServiceServer { 8 | constructor(private playerService: PlayerService) {} 9 | 10 | locate(call: ServerReadableStream, callback: sendUnaryData): void { 11 | 12 | } 13 | 14 | players(call: ServerWriteableStream): void { 15 | this.playerService.players() 16 | .consume(player => call.write(player), t => call.end(), () => call.end()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/grpc/SetupController.ts: -------------------------------------------------------------------------------- 1 | import {MapService} from "../../service"; 2 | import {GRPCServices, Map} from "game-idl"; 3 | import {sendUnaryData, ServerUnaryCall} from "grpc"; 4 | import {Empty} from "google-protobuf/google/protobuf/empty_pb"; 5 | 6 | export class GrpcSetupController implements GRPCServices.ISetupServiceServer { 7 | constructor(private mapService: MapService) {} 8 | 9 | get(call: ServerUnaryCall, callback: sendUnaryData) { 10 | callback(null, this.mapService.getMap()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/http/HttpExtrasController.ts: -------------------------------------------------------------------------------- 1 | import { Express, Request, Response } from 'express'; 2 | import * as SSE from 'express-sse'; 3 | import { ExtrasService } from '../../service'; 4 | 5 | 6 | export default (app: Express, extrasService: ExtrasService) => { 7 | app.get('http/extras', (req: Request, res: Response) => { 8 | const sse = new SSE.default(); 9 | sse.init(req, res); 10 | extrasService.extras() 11 | .doOnNext(sse.send) 12 | .consume(); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/http/HttpGameController.ts: -------------------------------------------------------------------------------- 1 | import {Express, Request, Response} from 'express'; 2 | import * as SSE from 'express-sse'; 3 | import {GameService} from '../../service'; 4 | 5 | export default (app: Express, gameService: GameService) => { 6 | app.post('/http/start', (req: Request, res: Response) => { 7 | const sse = new SSE.default(); 8 | sse.init(req, res); 9 | const nickname = req.body.toString(); 10 | return res.send(gameService.start(req.cookies.decode("uuid"), nickname)); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/http/HttpPlayerController.ts: -------------------------------------------------------------------------------- 1 | import {Express, Request, Response} from 'express'; 2 | import {PlayerService} from '../../service'; 3 | import {Location} from 'game-idl'; 4 | import * as SSE from 'express-sse'; 5 | 6 | export default (app: Express, playerService: PlayerService) => { 7 | 8 | app.post('/http/locate', (req: Request, res: Response) => { 9 | const location = req.body.location as Location; 10 | const uuid = req.cookies.decode("uuid"); 11 | 12 | playerService.locate(uuid, location); 13 | }); 14 | 15 | app.get('/http/players', (req: Request, res: Response) => { 16 | const sse = new SSE.default(); 17 | sse.init(req, res); 18 | 19 | playerService.players() 20 | .doOnNext(sse.send) 21 | .consume(); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/http/HttpSetupController.ts: -------------------------------------------------------------------------------- 1 | import { Express, Response, Request } from "express"; 2 | import { v4 } from "uuid"; 3 | import { MapService } from "../../service"; 4 | 5 | export default (app: Express, mapService: MapService) => { 6 | app.get("/http/setup", (req: Request, res: Response) => { 7 | const uuid = v4(); 8 | res.cookie("uuid", uuid); 9 | const map = mapService.getMap(); 10 | const buff = new Buffer(map.toString()).toString("base64"); 11 | res.send(buff); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/http/index.ts: -------------------------------------------------------------------------------- 1 | import { Express } from 'express'; 2 | import ExtrasController from './HttpExtrasController'; 3 | import GameController from './HttpGameController'; 4 | import PlayerController from './HttpPlayerController'; 5 | import SetupController from './HttpSetupController'; 6 | import { ExtrasService, GameService, PlayerService, MapService } from '../../service'; 7 | 8 | export default function controllers( 9 | app: Express, 10 | extrasService: ExtrasService, 11 | gameService: GameService, 12 | playerService: PlayerService, 13 | mapService: MapService, 14 | ): void { 15 | const extrasController = ExtrasController(app, extrasService); 16 | const gameController = GameController(app, gameService); 17 | const playerController = PlayerController(app, playerService); 18 | const setupController = SetupController(app, mapService); 19 | } 20 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/rsocket/ExtrasController.ts: -------------------------------------------------------------------------------- 1 | import * as rsocket_flowable from 'rsocket-flowable'; 2 | import * as google_protobuf_empty_pb from 'google-protobuf/google/protobuf/empty_pb'; 3 | import {Extra, RSocketRPCServices} from "game-idl"; 4 | import {ExtrasService} from '../../service'; 5 | 6 | export class ExtrasController implements RSocketRPCServices.ExtrasService { 7 | constructor(private extrasService: ExtrasService) {} 8 | 9 | extras(message: google_protobuf_empty_pb.Empty, metadata?: Buffer): rsocket_flowable.Flowable { 10 | return this.extrasService.extras() as any; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/rsocket/GameController.ts: -------------------------------------------------------------------------------- 1 | import * as rsocket_flowable from 'rsocket-flowable'; 2 | import {Config, Nickname, RSocketRPCServices} from "game-idl"; 3 | import {GameService} from '../../service'; 4 | 5 | export class GameController implements RSocketRPCServices.GameService { 6 | constructor(private readonly uuid: string, private gameService: GameService) {} 7 | 8 | start(message: Nickname, metadata?: Buffer): rsocket_flowable.Single { 9 | return rsocket_flowable.Single.of(this.gameService.start(this.uuid, message)); 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/rsocket/PlayerController.ts: -------------------------------------------------------------------------------- 1 | import * as rsocket_flowable from 'rsocket-flowable'; 2 | import * as google_protobuf_empty_pb from 'google-protobuf/google/protobuf/empty_pb'; 3 | import {Location, Player, RSocketRPCServices} from "game-idl"; 4 | import {PlayerService} from '../../service'; 5 | import {Disposable} from 'reactor-core-js'; 6 | import FlowableAdapter from './support/FlowableAdapter'; 7 | import {Flux} from 'reactor-core-js/flux'; 8 | 9 | export class PlayerController implements RSocketRPCServices.PlayerService { 10 | constructor(private playerService: PlayerService, private uuid: string) {} 11 | 12 | locate(messages: rsocket_flowable.Flowable, metadata?: Buffer): rsocket_flowable.Single { 13 | return new rsocket_flowable.Flowable(subject => { 14 | let disposable: Disposable = { 15 | dispose: () => {} 16 | }; 17 | 18 | subject.onSubscribe({ 19 | request: () => {}, 20 | cancel: () => disposable.dispose() 21 | }); 22 | 23 | disposable = Flux 24 | .from(FlowableAdapter.wrap(messages as any)) 25 | .consume( 26 | (location) => { 27 | this.playerService.locate(this.uuid, location); 28 | }, 29 | (e: Error) => subject.onError(e), 30 | () => subject.onComplete() 31 | ) 32 | }) as any; 33 | } 34 | 35 | players(message: google_protobuf_empty_pb.Empty, metadata?: Buffer): rsocket_flowable.Flowable { 36 | return this.playerService.players() as any; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/rsocket/SetupController.ts: -------------------------------------------------------------------------------- 1 | import { RSocketRPCServices } from "game-idl"; 2 | import { ExtrasService, GameService, MapService, PlayerService } from '../../service'; 3 | import { BufferEncoders } from 'rsocket-core' 4 | import RSocketWebSocketServer from 'rsocket-websocket-server' 5 | import { RequestHandlingRSocket } from 'rsocket-rpc-core' 6 | import { v4 } from 'uuid'; 7 | import { ConnectionStatus, ReactiveSocket } from 'rsocket-types'; 8 | import { GameController } from './GameController'; 9 | import { PlayerController } from './PlayerController'; 10 | import { ExtrasController } from './ExtrasController'; 11 | 12 | import { Server } from 'http'; 13 | 14 | export class SetupController { 15 | 16 | constructor( 17 | private mapService: MapService, 18 | private extrasService: ExtrasService, 19 | private playerService: PlayerService, 20 | private gameService: GameService, 21 | private server: Server 22 | ) { } 23 | 24 | handler(socket: ReactiveSocket) { 25 | const uuid = v4(); 26 | socket.connectionStatus() 27 | .subscribe((cs: ConnectionStatus) => { 28 | if (cs.kind == 'CLOSED' || cs.kind == 'ERROR') { 29 | this.playerService.disconnectPlayer(uuid); 30 | } 31 | }); 32 | const map = this.mapService.getMap(); 33 | new RSocketRPCServices.MapServiceClient(socket).setup(map); 34 | const handler = new RequestHandlingRSocket(); 35 | 36 | handler.addService('org.coinen.pacman.GameService', new RSocketRPCServices.GameServiceServer(new GameController(uuid, this.gameService))); 37 | handler.addService('org.coinen.pacman.PlayerService', new RSocketRPCServices.PlayerServiceServer(new PlayerController(this.playerService, uuid))); 38 | handler.addService('org.coinen.pacman.ExtrasService', new RSocketRPCServices.ExtrasServiceServer(new ExtrasController(this.extrasService))); 39 | return handler; 40 | } 41 | 42 | transport() { 43 | return new RSocketWebSocketServer( 44 | { 45 | server: this.server 46 | }, 47 | BufferEncoders 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/rsocket/support/FlowableAdapter.ts: -------------------------------------------------------------------------------- 1 | import { Publisher, Subscriber, Subscription } from "reactor-core-js/reactive-streams-spec"; 2 | 3 | export default class FlowableAdapter implements Publisher { 4 | 5 | static wrap(source: Publisher): FlowableAdapter { 6 | return new FlowableAdapter(source); 7 | } 8 | 9 | constructor(private source: Publisher) {} 10 | 11 | subscribe(s: Subscriber): void { 12 | this.source.subscribe({ 13 | onSubscribe: (subscription: Subscription) => { 14 | s.onSubscribe({ 15 | request: (n) => subscription.request(n > Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : n), 16 | cancel: () => subscription.cancel() 17 | }) 18 | }, 19 | onNext: (t: S) => s.onNext(t), 20 | onError: (e: Error) => s.onError(e), 21 | onComplete: () => s.onComplete(), 22 | }) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /game-server/src/main/typescript/controller/socket.io/socketio.ts: -------------------------------------------------------------------------------- 1 | import {Server} from 'socket.io'; 2 | import {ExtrasService, GameService, MapService, PlayerService} from "../../service"; 3 | import {Location, Nickname} from "game-idl"; 4 | 5 | export const socketIOSetup = ( 6 | mapService: MapService, 7 | extrasService: ExtrasService, 8 | playerService: PlayerService, 9 | gameService: GameService, 10 | server: Server) => { 11 | 12 | playerService 13 | .players() 14 | .consume(player => server.sockets.emit("players", new Buffer(player.serializeBinary()))); 15 | 16 | 17 | extrasService 18 | .extras() 19 | .consume(extra => server.sockets.emit("extras", new Buffer(extra.serializeBinary()))); 20 | 21 | server.on("connection", (socket) => { 22 | socket.on("disconnect", () => { 23 | playerService.disconnectPlayer(socket.id); 24 | }); 25 | 26 | socket.on("start", (data: ArrayBuffer, ack: Function) => { 27 | const nickname = Nickname.deserializeBinary(new Uint8Array(data)); 28 | 29 | ack(new Buffer(gameService.start(socket.id, nickname).serializeBinary())); 30 | }); 31 | 32 | socket.on("locate", (data: ArrayBuffer) => { 33 | const location = Location.deserializeBinary(new Uint8Array(data)); 34 | 35 | playerService.locate(socket.id,location); 36 | }); 37 | 38 | socket.emit("setup", new Buffer(mapService.getMap().serializeBinary())); 39 | }); 40 | }; -------------------------------------------------------------------------------- /game-server/src/main/typescript/repository/ExtrasRepository.ts: -------------------------------------------------------------------------------- 1 | export default interface ExtrasRepository { 2 | 3 | collideExtra(x: number, y: number): number; 4 | 5 | createExtra(size: number): number; 6 | 7 | findAll(): Set; 8 | 9 | saveAll(extras: number[]): void; 10 | } 11 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/repository/PlayerRepository.ts: -------------------------------------------------------------------------------- 1 | import { Player } from "game-idl"; 2 | 3 | export default interface PlayerRepository { 4 | 5 | findAll(): Player[]; 6 | 7 | findOne(uuid: string): Player; 8 | 9 | update(uuid: string, playerUpdater: (p1: Player) => Player): Player; 10 | 11 | save(uuid: string, playerSupplier: () => Player): Player; 12 | 13 | delete(uuid: string): Player; 14 | } 15 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/repository/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ExtrasRepository } from './ExtrasRepository'; 2 | export { default as PlayerRepository } from './PlayerRepository'; 3 | export { default as InMemoryExtrasRepository } from './support/InMemoryExtrasRepository'; 4 | export { default as InMemoryPlayerRepository } from './support/InMemoryPlayerRepository'; 5 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/repository/support/InMemoryExtrasRepository.ts: -------------------------------------------------------------------------------- 1 | import ExtrasRepository from '../ExtrasRepository'; 2 | 3 | export default class InMemoryExtrasRepository implements ExtrasRepository { 4 | 5 | tileSize = 100; 6 | mapHeight = 60; 7 | mapWidth = 60; 8 | offset = 11; 9 | 10 | bitmap: Set = new Set(); 11 | 12 | collideExtra(x: number, y: number): number { 13 | console.log('bitmap', x, y); 14 | let i = Math.round(x / this.tileSize); 15 | let j = Math.round(y / this.tileSize); 16 | 17 | const flattenPosition = i + j * this.mapWidth; 18 | if (this.bitmap.has(flattenPosition)) { 19 | this.bitmap.delete(flattenPosition); 20 | return flattenPosition; 21 | } else if (this.bitmap.has(-flattenPosition)) { 22 | this.bitmap.delete(flattenPosition); 23 | return -flattenPosition; 24 | } 25 | 26 | return 0; 27 | } 28 | 29 | 30 | 31 | createExtra(size: number): number { 32 | let nextPosition = 0; 33 | do { 34 | nextPosition = InMemoryExtrasRepository.randomPosition(this.mapWidth, this.mapHeight, this.offset); 35 | } while (this.bitmap.has(nextPosition) || this.bitmap.has(-nextPosition)); 36 | 37 | let powerupCount = 0; 38 | this.bitmap.forEach(el => Math.sign(el) < 0 ? powerupCount++ : {}); 39 | 40 | var powerupAllowed = false; 41 | var totalSpace = 42 | (this.mapWidth - 2 * this.offset + 1) * (this.mapHeight - 2 * this.offset + 1); 43 | if (size <= 2) { 44 | powerupAllowed = powerupCount < Math.ceil(totalSpace / 360); 45 | } 46 | else if (size <= 4) { 47 | powerupAllowed = powerupCount < Math.ceil(totalSpace / 900); 48 | } 49 | else { 50 | powerupAllowed = powerupCount == 0 && Math.random() < 0.02; 51 | } 52 | 53 | if (powerupAllowed) { 54 | this.bitmap.add(-nextPosition); 55 | return -nextPosition; 56 | } 57 | else { 58 | this.bitmap.add(nextPosition); 59 | return nextPosition; 60 | } 61 | } 62 | 63 | findAll(): Set { 64 | return this.bitmap; 65 | } 66 | 67 | saveAll(extras: number[]): void { 68 | extras.forEach(extra => { 69 | this.bitmap.add(extra); 70 | }); 71 | console.log('extras', this.bitmap); 72 | } 73 | 74 | 75 | static randomPosition(width: number, height: number, offset: number): number { 76 | return (Math.floor(Math.random() * (width - 2 * offset + 1) + offset) + Math.floor(Math.random() * (height - 2 * offset + 1) + offset) * height); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/repository/support/InMemoryPlayerRepository.ts: -------------------------------------------------------------------------------- 1 | import PlayerRepository from '../PlayerRepository'; 2 | import { Player } from 'game-idl'; 3 | 4 | export default class InMemoryPlayerRepository implements PlayerRepository { 5 | store: Map = new Map() 6 | 7 | findAll(): Player[] { 8 | return Array.from(this.store.values()); 9 | } 10 | 11 | findOne(uuid: string): Player { 12 | return this.store.get(uuid); 13 | } 14 | 15 | update(uuid: string, supplier: (p: Player) => Player) { 16 | let user = this.store.get(uuid); 17 | user = supplier(user); 18 | this.store.set(uuid, user); 19 | return user; 20 | } 21 | 22 | save(uuid: string, supplier: () => Player): Player { 23 | const user = supplier(); 24 | user.setUuid(uuid); 25 | this.store.set(uuid, user); 26 | return user; 27 | } 28 | 29 | delete(uuid: string): Player { 30 | const user = this.store.get(uuid); 31 | this.store.delete(uuid); 32 | return user; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/service/ExtrasService.ts: -------------------------------------------------------------------------------- 1 | import { Flux } from 'reactor-core-js/flux'; 2 | import { Extra } from 'game-idl'; 3 | 4 | export default interface ExtrasService { 5 | 6 | extras(): Flux; 7 | 8 | check(x: number, y: number): number; 9 | 10 | isPowerupActive(): boolean; 11 | } 12 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/service/GameService.ts: -------------------------------------------------------------------------------- 1 | import {Config, Nickname} from 'game-idl'; 2 | 3 | export default interface GameService { 4 | 5 | start(uuid: string, nickname: Nickname): Config; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/service/MapService.ts: -------------------------------------------------------------------------------- 1 | import {Map, Point} from 'game-idl'; 2 | 3 | export default interface MapService { 4 | 5 | getMap(): Map; 6 | 7 | getTilesPositions(): Point[]; 8 | 9 | getRandomPoint(): Point; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/service/PlayerService.ts: -------------------------------------------------------------------------------- 1 | import {Flux} from 'reactor-core-js/flux'; 2 | import {Location, Player} from 'game-idl'; 3 | 4 | export default interface PlayerService { 5 | 6 | createPlayer(uuid: string, nickname: string): Player; 7 | 8 | disconnectPlayer(uuid: string): void; 9 | 10 | locate(uuid: string, location: Location): void; 11 | 12 | players(): Flux ; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/service/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ExtrasService } from './ExtrasService'; 2 | export { default as GameService } from './GameService'; 3 | export { default as MapService } from './MapService'; 4 | export { default as PlayerService } from './PlayerService'; 5 | 6 | export { default as DefaultExtrasService } from './support/DefaultExtrasService'; 7 | export { default as DefaultGameService } from './support/DefaultGameService'; 8 | export { default as DefaultMapService } from './support/DefaultMapService'; 9 | export { default as DefaultPlayerService } from './support/DefaultPlayerService'; 10 | -------------------------------------------------------------------------------- /game-server/src/main/typescript/service/support/DefaultGameService.ts: -------------------------------------------------------------------------------- 1 | import {GameService, PlayerService} from '../index'; 2 | import {Config, Nickname, Player, Score} from 'game-idl'; 3 | import {ExtrasRepository, PlayerRepository} from '../../repository'; 4 | 5 | export default class DefaultGameService implements GameService { 6 | constructor( 7 | private playerService: PlayerService, 8 | private extrasRepository: ExtrasRepository, 9 | private playerRepository: PlayerRepository, 10 | ) { 11 | 12 | } 13 | 14 | start(uuid: string, nickname: Nickname): Config { 15 | if (nickname.getValue().length <= 13) { 16 | let name = nickname.getValue().replace(/[^a-zA-Z0-9. ]/g, ''); 17 | if (name.length === 0) { 18 | name = 'Boss of this gym'; 19 | } 20 | const player = this.playerService.createPlayer(uuid, name); 21 | const config = new Config(); 22 | config.setPlayer(player); 23 | config.setPlayersList(this.playerRepository.findAll() 24 | .filter((p: Player) => p.getUuid() !== player.getUuid()) 25 | ); 26 | config.setExtrasList(Array.from(this.extrasRepository.findAll())); 27 | config.setScoresList(this.playerRepository.findAll() 28 | .map((p: Player) => { 29 | const score = new Score(); 30 | score.setUsername(p.getNickname()); 31 | score.setScore(p.getScore()); 32 | score.setUuid(p.getUuid()); 33 | return score; 34 | })); 35 | console.log(config.toObject().extrasList); 36 | return config; 37 | } else { 38 | throw new Error('Invalid nickname'); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /game-server/src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | 2 | log4j.rootLogger=DEBUG, STDOUT 3 | log4j.logger.deng=INFO 4 | log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender 5 | log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout 6 | log4j.appender.STDOUT.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n -------------------------------------------------------------------------------- /game-server/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /game-server/src/test/resources/logging.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO, stdout 2 | io.grpc.level=INFO -------------------------------------------------------------------------------- /game-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build", 4 | "noImplicitAny": true, 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "target": "es5", 8 | "downlevelIteration": true, 9 | "sourceMap": true, 10 | "allowJs": true, 11 | "typeRoots": [ 12 | "src/main/@types/typescript", 13 | "node_modules/@types/" 14 | ], 15 | "lib": [ 16 | "es6", 17 | "scripthost", 18 | "dom" 19 | ] 20 | }, 21 | "include": [ 22 | "./src/main/typescript" 23 | ], 24 | "exclude": [ 25 | "./build", 26 | "./src/main/@types/**/*", 27 | "./node_modules/**/*" 28 | ] 29 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CollaborationInEncapsulation/reactive-pacman/c6a859f051a71ecad6a4ac2fa4770718682b2001/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Feb 22 09:43:36 EET 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "game-idl", 4 | "game-client", 5 | "metrics-idl", 6 | "metrics-client" 7 | ], 8 | "version": "0.0.1" 9 | } 10 | -------------------------------------------------------------------------------- /metrics-client/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | 3 | implementation project(':metrics-idl') 4 | implementation "io.grpc:grpc-stub" 5 | implementation "io.grpc:grpc-protobuf" 6 | implementation 'io.rsocket:rsocket-core' 7 | implementation 'io.micrometer:micrometer-core' 8 | implementation 'com.google.protobuf:protobuf-java' 9 | implementation 'org.springframework.boot:spring-boot-starter' 10 | 11 | testImplementation 'io.projectreactor:reactor-test' 12 | } -------------------------------------------------------------------------------- /metrics-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metrics-client", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "build/src/main/typescript/index.js", 6 | "types": "build/src/main/typescript/index.d.ts", 7 | "scripts": { 8 | "postinstall": "npm run build", 9 | "build": "tsc" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "metrics-idl": "^0.0.1", 15 | "@types/rsocket-types": "^0.0.1", 16 | "@types/rsocket-core": "^0.0.2", 17 | "@types/rsocket-flowable": "^0.0.2", 18 | "@types/google-protobuf": "^3.7.1", 19 | "@types/node": "^12.6.2", 20 | "google-protobuf": "^3.9.0", 21 | "reactor-core-js": "^0.5.0", 22 | "rsocket-flowable": "^0.0.10", 23 | "rsocket-rpc-core": "^0.0.5-0", 24 | "rsocket-rpc-metrics": "^0.0.1", 25 | "rsocket-rpc-tracing": "^0.0.3", 26 | "rsocket-types": "^0.0.10" 27 | }, 28 | "devDependencies": { 29 | "typescript": "^3.5.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /metrics-client/src/@types/typescript/reactor-core-js/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'reactor-core-js/flux' { 2 | import { Disposable } from "reactor-core-js"; 3 | import { Publisher, Subscriber, Subscription } from "reactor-core-js/reactive-streams-spec"; 4 | 5 | export class Flux implements Publisher { 6 | subscribe(s: Subscriber): void; 7 | 8 | static from(stream: Publisher): Flux; 9 | 10 | 11 | 12 | 13 | map (mapper: (t: T) => V): Flux; 14 | doOnNext(callback: (t: T) => void): Flux; 15 | 16 | 17 | 18 | compose(transformer: (flux: Flux) => Publisher): Flux 19 | 20 | consume(): Disposable; 21 | consume(onNextCallback: (t: T) => void): Disposable; 22 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void): Disposable; 23 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void, onCompleteCallback: () => void): Disposable; 24 | } 25 | 26 | export class DirectProcessor extends Flux implements Subscriber { 27 | onSubscribe(s: Subscription): void; 28 | onNext(t: T): void; 29 | onError(t: Error): void; 30 | onComplete(): void; 31 | } 32 | // } 33 | 34 | // declare module 'reactor-core-js/mono' { 35 | 36 | export class Mono implements Publisher { 37 | subscribe(): void 38 | } 39 | } 40 | 41 | declare module 'reactor-core-js' { 42 | export interface Disposable { 43 | dispose(): void; 44 | } 45 | } 46 | 47 | declare module 'reactor-core-js/reactive-streams-spec' { 48 | export interface Subscription { 49 | request(n: number): void; 50 | cancel(): void; 51 | } 52 | 53 | export interface Subscriber { 54 | onSubscribe(s: Subscription): void; 55 | onNext(t: T): void; 56 | onError(t: Error): void; 57 | onComplete(): void; 58 | } 59 | 60 | export interface Publisher { 61 | subscribe(s: Subscriber): void; 62 | } 63 | 64 | export interface Processor extends Subscriber, Publisher { } 65 | } -------------------------------------------------------------------------------- /metrics-client/src/@types/typescript/reactor-core-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "@types/reactor-core-js@*", 3 | "_id": "@types/reactor-core-js@4.16.0", 4 | "_inBundle": false, 5 | "_integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==", 6 | "_location": "/@types/reactor-core-js", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "@types/reactor-core-js@*", 12 | "name": "@types/reactor-core-js", 13 | "escapedName": "@types%2freactor-core-js", 14 | "scope": "@types", 15 | "rawSpec": "*", 16 | "saveSpec": null, 17 | "fetchSpec": "*" 18 | }, 19 | "_requiredBy": [ 20 | "/@types/express", 21 | "/@types/serve-static" 22 | ], 23 | "_resolved": "https://registry.npmjs.org/@types/reactor-core-js/-/reactor-core-js-4.16.0.tgz", 24 | "_shasum": "fdfe777594ddc1fe8eb8eccce52e261b496e43e7", 25 | "_spec": "@types/reactor-core-js@*", 26 | "_where": "/Users/Shadowgun/Documents/Workspace/Java/dinoman-io/node_modules/@types/express", 27 | "bugs": { 28 | "url": "https://github.com/DefinitelyTyped/DefinitelyTyped/issues" 29 | }, 30 | "bundleDependencies": false, 31 | "contributors": [ 32 | { 33 | "name": "Boris Yankov", 34 | "url": "https://github.com/borisyankov" 35 | }, 36 | { 37 | "name": "Michał Lytek", 38 | "url": "https://github.com/19majkel94" 39 | }, 40 | { 41 | "name": "Kacper Polak", 42 | "url": "https://github.com/kacepe" 43 | }, 44 | { 45 | "name": "Satana Charuwichitratana", 46 | "url": "https://github.com/micksatana" 47 | }, 48 | { 49 | "name": "Sami Jaber", 50 | "url": "https://github.com/samijaber" 51 | } 52 | ], 53 | "dependencies": { 54 | "@types/events": "*", 55 | "@types/node": "*", 56 | "@types/range-parser": "*" 57 | }, 58 | "deprecated": false, 59 | "description": "TypeScript definitions for Express", 60 | "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped#readme", 61 | "license": "MIT", 62 | "main": "", 63 | "name": "@types/reactor-core-js", 64 | "repository": { 65 | "type": "git", 66 | "url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git" 67 | }, 68 | "scripts": {}, 69 | "typeScriptVersion": "2.2", 70 | "typesPublisherContentHash": "8be76390251659a06700a35c7e64af43c3207cbd3c16a34cc7491535b5535aa8", 71 | "version": "4.16.0" 72 | } 73 | -------------------------------------------------------------------------------- /metrics-client/src/@types/typescript/rsocket-rpc-core/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'rsocket-rpc-core' { 2 | import { PayloadSerializers } from "rsocket-core"; 3 | import { Encodable, DuplexConnection, Responder, ReactiveSocket, Payload } from "rsocket-types"; 4 | import { Single, Flowable } from "rsocket-flowable"; 5 | 6 | export type ClientConfig = { 7 | serializers?: PayloadSerializers, 8 | setup: { 9 | keepAlive: number, 10 | lifetime: number, 11 | metadata?: Encodable, 12 | }, 13 | transport: DuplexConnection, 14 | responder?: Responder, 15 | } 16 | 17 | export class RpcClient { 18 | constructor(config: ClientConfig); 19 | close(): void; 20 | connect(): Single>; 21 | } 22 | 23 | export class RequestHandlingRSocket implements Responder { 24 | 25 | addService(service: string, handler: Responder): void; 26 | 27 | fireAndForget(payload: Payload): void; 28 | 29 | requestResponse(payload: Payload,): Single> 30 | 31 | requestStream(payload: Payload,): Flowable>; 32 | 33 | requestChannel(payloads: Flowable>,): Flowable>; 34 | 35 | metadataPush(payload: Payload): Single; 36 | } 37 | } -------------------------------------------------------------------------------- /metrics-client/src/main/java/org/coinen/reactive/pacman/metrics/grpc/ClientMetricsInterceptor.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.grpc; 2 | 3 | import java.time.Instant; 4 | 5 | import com.google.protobuf.Message; 6 | import com.google.protobuf.UnknownFieldSet; 7 | import io.grpc.CallOptions; 8 | import io.grpc.Channel; 9 | import io.grpc.ClientCall; 10 | import io.grpc.ClientInterceptor; 11 | import io.grpc.ForwardingClientCall; 12 | import io.grpc.Metadata; 13 | import io.grpc.MethodDescriptor; 14 | 15 | public class ClientMetricsInterceptor implements ClientInterceptor { 16 | 17 | @Override 18 | public ClientCall interceptCall( 19 | MethodDescriptor method, 20 | CallOptions callOptions, 21 | Channel next 22 | ) { 23 | 24 | return new ForwardingClientCall.SimpleForwardingClientCall(next.newCall(method, callOptions)) { 25 | @Override 26 | @SuppressWarnings("unchecked") 27 | public void sendMessage(ReqT message) { 28 | if (message instanceof Message) { 29 | Instant now = Instant.now(); 30 | message = (ReqT) ((Message) message) 31 | .toBuilder() 32 | .setUnknownFields( 33 | UnknownFieldSet 34 | .newBuilder() 35 | .addField( 36 | 9999, 37 | UnknownFieldSet.Field 38 | .newBuilder() 39 | .addFixed64(now.getEpochSecond()) 40 | .addFixed32(now.getNano()) 41 | .build() 42 | ) 43 | .build() 44 | ) 45 | .build(); 46 | } 47 | super.sendMessage(message); 48 | } 49 | 50 | @Override 51 | public void start(Listener responseListener, Metadata headers) { 52 | headers.put(Constants.INSTANT_KEY, Instant.now()); 53 | super.start(responseListener, headers); 54 | } 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /metrics-client/src/main/java/org/coinen/reactive/pacman/metrics/grpc/Constants.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.grpc; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.time.Instant; 5 | 6 | import io.grpc.Metadata; 7 | 8 | public abstract class Constants { 9 | 10 | private static class InstantBinaryMarshaller 11 | implements Metadata.BinaryMarshaller { 12 | 13 | private static ThreadLocal THREAD_LOCAL_BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocate(Long.BYTES + Integer.BYTES)); 14 | 15 | @Override 16 | public byte[] toBytes(Instant value) { 17 | long second = value.getEpochSecond(); 18 | int nano = value.getNano(); 19 | 20 | return THREAD_LOCAL_BUFFER 21 | .get() 22 | .putLong(0, second) 23 | .putInt(Long.BYTES, nano) 24 | .array(); 25 | } 26 | 27 | @Override 28 | public Instant parseBytes(byte[] serialized) { 29 | ByteBuffer byteBuffer = THREAD_LOCAL_BUFFER 30 | .get() 31 | .put(serialized, 0, Long.BYTES + Integer.BYTES) 32 | .flip(); 33 | 34 | long seconds = byteBuffer.getLong(0); 35 | int nanos = byteBuffer.getInt(Long.BYTES); 36 | 37 | return Instant.ofEpochSecond(seconds, nanos); 38 | } 39 | } 40 | 41 | static final Metadata.Key INSTANT_KEY = Metadata.Key.of("time-bin", new InstantBinaryMarshaller()); 42 | } 43 | -------------------------------------------------------------------------------- /metrics-client/src/main/java/org/coinen/reactive/pacman/metrics/rsocket/ClientMetricsAwareRSocket.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.rsocket; 2 | 3 | import java.util.function.Function; 4 | 5 | import io.micrometer.core.instrument.Counter; 6 | import io.micrometer.core.instrument.MeterRegistry; 7 | import io.micrometer.core.instrument.Timer; 8 | import io.rsocket.Payload; 9 | import io.rsocket.RSocket; 10 | import io.rsocket.util.RSocketProxy; 11 | import org.reactivestreams.Publisher; 12 | import reactor.core.publisher.Flux; 13 | import reactor.core.publisher.Mono; 14 | 15 | public class ClientMetricsAwareRSocket extends RSocketProxy { 16 | 17 | private final Function measured; 18 | 19 | public ClientMetricsAwareRSocket(RSocket source) { 20 | super(source); 21 | this.measured = Function.identity(); 22 | } 23 | 24 | public ClientMetricsAwareRSocket(RSocket source, MeterRegistry registry, String prefix) { 25 | super(source); 26 | Counter counter = Counter.builder(prefix + ".rsocket.client.end.to.end.throughput") 27 | .register(registry); 28 | Timer timer = Timer.builder(prefix + ".rsocket.client.end.to.end.latency") 29 | .publishPercentiles(0.5, 0.9, 0.95, 0.99) 30 | .register(registry); 31 | this.measured = ReactiveMetrics.measured(timer, counter); 32 | } 33 | 34 | @Override 35 | public Mono fireAndForget(Payload payload) { 36 | return super.fireAndForget(ReactiveMetrics.timed(payload)); 37 | } 38 | 39 | @Override 40 | public Mono requestResponse(Payload payload) { 41 | return super.requestResponse(ReactiveMetrics.timed(payload)) 42 | .map(measured); 43 | } 44 | 45 | @Override 46 | public Flux requestStream(Payload payload) { 47 | return super.requestStream(ReactiveMetrics.timed(payload)) 48 | .map(measured); 49 | } 50 | 51 | @Override 52 | public Flux requestChannel(Publisher payloads) { 53 | return Flux 54 | .from(payloads) 55 | .map(ReactiveMetrics::timed) 56 | .transform(super::requestChannel) 57 | .map(measured); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /metrics-client/src/main/java/org/coinen/reactive/pacman/metrics/rsocket/ReactiveMetrics.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.rsocket; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.function.Function; 6 | 7 | import io.micrometer.core.instrument.Counter; 8 | import io.micrometer.core.instrument.Timer; 9 | import io.netty.buffer.ByteBuf; 10 | import io.netty.buffer.ByteBufAllocator; 11 | import io.rsocket.Payload; 12 | import io.rsocket.util.ByteBufPayload; 13 | 14 | abstract class ReactiveMetrics { 15 | 16 | private ReactiveMetrics() {} 17 | 18 | static Payload timed(Payload p) { 19 | try { 20 | return ByteBufPayload.create(p.data().retain(), 21 | TimestampMetadata.encode(ByteBufAllocator.DEFAULT, Instant.now(), p.metadata())); 22 | } 23 | finally { 24 | p.release(); 25 | } 26 | } 27 | 28 | static Function measured(Timer timer, Counter counter) { 29 | return payload -> { 30 | ByteBuf metadata = payload.metadata(); 31 | Instant time = TimestampMetadata.time(metadata); 32 | 33 | if (time != null) { 34 | Instant now = Instant.now(); 35 | Duration duration = Duration.between(time, now); 36 | timer.record(duration); 37 | } 38 | 39 | counter.increment(); 40 | 41 | return payload; 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /metrics-client/src/main/java/org/coinen/reactive/pacman/metrics/rsocket/TimestampMetadata.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.rsocket; 2 | 3 | import java.time.Instant; 4 | 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.ByteBufAllocator; 7 | import io.netty.buffer.Unpooled; 8 | 9 | abstract class TimestampMetadata { 10 | 11 | 12 | private static final long MAX_SECOND = 31556889864403199L; 13 | 14 | private static final int METADATA_OFFSET = Long.BYTES + Integer.BYTES; 15 | 16 | private TimestampMetadata() {} 17 | 18 | 19 | static ByteBuf encode(ByteBufAllocator allocator, Instant time, ByteBuf metadata) { 20 | ByteBuf byteBuf = allocator.buffer(); 21 | 22 | byteBuf.writeBytes(metadata, 0, metadata.readableBytes()); 23 | 24 | byteBuf.writeLong(time.getEpochSecond()); 25 | byteBuf.writeInt(time.getNano()); 26 | 27 | return byteBuf; 28 | } 29 | 30 | static Instant time(ByteBuf metadata) { 31 | int metadataLength = metadata.readableBytes() - METADATA_OFFSET; 32 | 33 | if (metadataLength >= 0) { 34 | long seconds = metadata.getLong(metadataLength); 35 | int nanos = metadata.getInt(metadataLength + Long.BYTES); 36 | 37 | if (seconds > 0 && seconds < MAX_SECOND&& nanos >= 0) { 38 | return Instant.ofEpochSecond(seconds, nanos); 39 | } 40 | } 41 | 42 | return null; 43 | } 44 | 45 | static ByteBuf metadata(ByteBuf byteBuf) { 46 | int metadataLength = byteBuf.readableBytes() - METADATA_OFFSET; 47 | 48 | return metadataLength > 0 ? byteBuf.slice(METADATA_OFFSET, metadataLength) : Unpooled.EMPTY_BUFFER; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /metrics-client/src/main/typescript/ReactiveMetricsRegistry.ts: -------------------------------------------------------------------------------- 1 | import { SimpleMeterRegistry, IMeter } from "rsocket-rpc-metrics"; 2 | import { MetricsSnapshot } from "metrics-idl" 3 | import { ISubscription, ISubscriber } from "rsocket-types"; 4 | import { Flowable } from "rsocket-flowable"; 5 | import { convert } from "./MappingUtils"; 6 | 7 | 8 | export default class ReactiveMetricsRegistry extends SimpleMeterRegistry { 9 | 10 | private requestedFromDownstream: number; 11 | private snapshotSubscriber: ISubscriber; 12 | 13 | constructor() { 14 | super(); 15 | 16 | setInterval(() => { 17 | const sink = this.snapshotSubscriber; 18 | const requested = this.requestedFromDownstream; 19 | 20 | if (sink && requested > 0) { 21 | sink.onNext( 22 | this.meters() 23 | .reduce( 24 | (ms: MetricsSnapshot, meter: IMeter) => { 25 | convert(meter).forEach(m => ms.addMeters(m)); 26 | return ms; 27 | }, 28 | new MetricsSnapshot(), 29 | ) 30 | ); 31 | this.requestedFromDownstream--; 32 | } 33 | }, 300) 34 | } 35 | 36 | asFlowable(): Flowable { 37 | return new Flowable((s: ISubscriber) => { 38 | if (this.snapshotSubscriber) { 39 | s.onSubscribe({ 40 | cancel: () => { }, 41 | request: (n) => { } 42 | }); 43 | s.onError(new Error("Allowed only a single subscriber")); 44 | return; 45 | } 46 | 47 | this.requestedFromDownstream = 0; 48 | this.snapshotSubscriber = s; 49 | 50 | const subscription: ISubscription = { 51 | cancel: () => { 52 | this.snapshotSubscriber = undefined; 53 | }, 54 | request: (n: number) => { 55 | this.requestedFromDownstream += n; 56 | } 57 | }; 58 | 59 | this.snapshotSubscriber.onSubscribe(subscription); 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /metrics-client/src/main/typescript/index.ts: -------------------------------------------------------------------------------- 1 | import ReactiveMetricsRegistry from "./ReactiveMetricsRegistry"; 2 | import * as MappingUtils from "./MappingUtils"; 3 | 4 | export { 5 | MappingUtils, 6 | ReactiveMetricsRegistry 7 | } -------------------------------------------------------------------------------- /metrics-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build", 4 | "declaration": true, 5 | "noImplicitAny": true, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "target": "es5", 9 | "downlevelIteration": true, 10 | "allowJs": false, 11 | "rootDir": "", 12 | "typeRoots": [ 13 | "node_modules/@types", 14 | "./src/@types/typescript" 15 | ], 16 | "lib": [ 17 | "es6", 18 | "scripthost", 19 | "dom" 20 | ] 21 | }, 22 | "files": [ 23 | "src/main/typescript/index.ts" 24 | ], 25 | "include": [ 26 | "src/**/*" 27 | ] 28 | } -------------------------------------------------------------------------------- /metrics-idl/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.google.protobuf' 3 | } 4 | 5 | dependencies { 6 | implementation 'com.google.protobuf:protobuf-java' 7 | implementation 'com.google.protobuf:protobuf-java-util' 8 | implementation 'io.grpc:grpc-protobuf' 9 | implementation 'io.rsocket.rpc:rsocket-rpc-core' 10 | implementation "com.salesforce.servicelibs:reactor-grpc-stub" 11 | 12 | protobuf 'io.rsocket.rpc:rsocket-rpc-protobuf-idl' 13 | } 14 | 15 | sourceSets { 16 | main { 17 | proto { srcDir 'src/main/proto' } 18 | } 19 | } 20 | 21 | protobuf { 22 | generatedFilesBaseDir = "${projectDir}/src/generated" 23 | 24 | protoc { 25 | artifact = 'com.google.protobuf:protoc' 26 | } 27 | 28 | plugins { 29 | grpc { 30 | artifact = "io.grpc:protoc-gen-grpc-java" 31 | } 32 | reactorGRpc { 33 | artifact = "com.salesforce.servicelibs:reactor-grpc:0.10.0-RC1:jdk8@jar" 34 | } 35 | rsocketRpc { 36 | artifact = "io.rsocket.rpc:rsocket-rpc-protobuf" 37 | } 38 | } 39 | 40 | generateProtoTasks { 41 | ofSourceSet('main')*.plugins { 42 | grpc {} 43 | rsocketRpc {} 44 | reactorGRpc {} 45 | } 46 | } 47 | } 48 | 49 | idea { 50 | module { 51 | sourceDirs += file("src/main/proto") 52 | sourceDirs += file("src/generated/main/java") 53 | sourceDirs += file("src/generated/main/grpc") 54 | sourceDirs += file("src/generated/main/reactorGRpc") 55 | sourceDirs += file("src/generated/main/rsocketRpc") 56 | 57 | generatedSourceDirs += file('src/generated/main/java') 58 | generatedSourceDirs += file("src/generated/main/grpc") 59 | generatedSourceDirs += file("src/generated/main/reactorGRpc") 60 | generatedSourceDirs += file("src/generated/main/rsocketRpc") 61 | } 62 | } 63 | 64 | clean { 65 | delete protobuf.generatedFilesBaseDir 66 | } 67 | -------------------------------------------------------------------------------- /metrics-idl/src/@types/typescript/reactor-core-js/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'reactor-core-js/flux' { 2 | import { Disposable } from "reactor-core-js"; 3 | import { Publisher, Subscriber, Subscription } from "reactor-core-js/reactive-streams-spec"; 4 | 5 | export class Flux implements Publisher { 6 | subscribe(s: Subscriber): void; 7 | 8 | static from(stream: Publisher): Flux; 9 | 10 | 11 | 12 | 13 | map (mapper: (t: T) => V): Flux; 14 | doOnNext(callback: (t: T) => void): Flux; 15 | 16 | 17 | 18 | compose(transformer: (flux: Flux) => Publisher): Flux 19 | 20 | consume(): Disposable; 21 | consume(onNextCallback: (t: T) => void): Disposable; 22 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void): Disposable; 23 | consume(onNextCallback: (t: T) => void, onErrorCallback: (e: Error) => void, onCompleteCallback: () => void): Disposable; 24 | } 25 | 26 | export class DirectProcessor extends Flux implements Subscriber { 27 | onSubscribe(s: Subscription): void; 28 | onNext(t: T): void; 29 | onError(t: Error): void; 30 | onComplete(): void; 31 | } 32 | // } 33 | 34 | // declare module 'reactor-core-js/mono' { 35 | 36 | export class Mono implements Publisher { 37 | subscribe(): void 38 | } 39 | } 40 | 41 | declare module 'reactor-core-js' { 42 | export interface Disposable { 43 | dispose(): void; 44 | } 45 | } 46 | 47 | declare module 'reactor-core-js/reactive-streams-spec' { 48 | export interface Subscription { 49 | request(n: number): void; 50 | cancel(): void; 51 | } 52 | 53 | export interface Subscriber { 54 | onSubscribe(s: Subscription): void; 55 | onNext(t: T): void; 56 | onError(t: Error): void; 57 | onComplete(): void; 58 | } 59 | 60 | export interface Publisher { 61 | subscribe(s: Subscriber): void; 62 | } 63 | 64 | export interface Processor extends Subscriber, Publisher { } 65 | } -------------------------------------------------------------------------------- /metrics-idl/src/@types/typescript/reactor-core-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "@types/reactor-core-js@*", 3 | "_id": "@types/reactor-core-js@4.16.0", 4 | "_inBundle": false, 5 | "_integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==", 6 | "_location": "/@types/reactor-core-js", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "range", 10 | "registry": true, 11 | "raw": "@types/reactor-core-js@*", 12 | "name": "@types/reactor-core-js", 13 | "escapedName": "@types%2freactor-core-js", 14 | "scope": "@types", 15 | "rawSpec": "*", 16 | "saveSpec": null, 17 | "fetchSpec": "*" 18 | }, 19 | "_requiredBy": [ 20 | "/@types/express", 21 | "/@types/serve-static" 22 | ], 23 | "_resolved": "https://registry.npmjs.org/@types/reactor-core-js/-/reactor-core-js-4.16.0.tgz", 24 | "_shasum": "fdfe777594ddc1fe8eb8eccce52e261b496e43e7", 25 | "_spec": "@types/reactor-core-js@*", 26 | "_where": "/Users/Shadowgun/Documents/Workspace/Java/dinoman-io/node_modules/@types/express", 27 | "bugs": { 28 | "url": "https://github.com/DefinitelyTyped/DefinitelyTyped/issues" 29 | }, 30 | "bundleDependencies": false, 31 | "contributors": [ 32 | { 33 | "name": "Boris Yankov", 34 | "url": "https://github.com/borisyankov" 35 | }, 36 | { 37 | "name": "Michał Lytek", 38 | "url": "https://github.com/19majkel94" 39 | }, 40 | { 41 | "name": "Kacper Polak", 42 | "url": "https://github.com/kacepe" 43 | }, 44 | { 45 | "name": "Satana Charuwichitratana", 46 | "url": "https://github.com/micksatana" 47 | }, 48 | { 49 | "name": "Sami Jaber", 50 | "url": "https://github.com/samijaber" 51 | } 52 | ], 53 | "dependencies": { 54 | "@types/events": "*", 55 | "@types/node": "*", 56 | "@types/range-parser": "*" 57 | }, 58 | "deprecated": false, 59 | "description": "TypeScript definitions for Express", 60 | "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped#readme", 61 | "license": "MIT", 62 | "main": "", 63 | "name": "@types/reactor-core-js", 64 | "repository": { 65 | "type": "git", 66 | "url": "git+https://github.com/DefinitelyTyped/DefinitelyTyped.git" 67 | }, 68 | "scripts": {}, 69 | "typeScriptVersion": "2.2", 70 | "typesPublisherContentHash": "8be76390251659a06700a35c7e64af43c3207cbd3c16a34cc7491535b5535aa8", 71 | "version": "4.16.0" 72 | } 73 | -------------------------------------------------------------------------------- /metrics-idl/src/@types/typescript/rsocket-rpc-core/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'rsocket-rpc-core' { 2 | import { PayloadSerializers } from "rsocket-core"; 3 | import { Encodable, DuplexConnection, Responder, ReactiveSocket, Payload } from "rsocket-types"; 4 | import { Single, Flowable } from "rsocket-flowable"; 5 | 6 | export type ClientConfig = { 7 | serializers?: PayloadSerializers, 8 | setup: { 9 | keepAlive: number, 10 | lifetime: number, 11 | metadata?: Encodable, 12 | }, 13 | transport: DuplexConnection, 14 | responder?: Responder, 15 | } 16 | 17 | export class RpcClient { 18 | constructor(config: ClientConfig); 19 | close(): void; 20 | connect(): Single>; 21 | } 22 | 23 | export class RequestHandlingRSocket implements Responder { 24 | 25 | addService(service: string, handler: Responder): void; 26 | 27 | fireAndForget(payload: Payload): void; 28 | 29 | requestResponse(payload: Payload,): Single> 30 | 31 | requestStream(payload: Payload,): Flowable>; 32 | 33 | requestChannel(payloads: Flowable>,): Flowable>; 34 | 35 | metadataPush(payload: Payload): Single; 36 | } 37 | } -------------------------------------------------------------------------------- /metrics-idl/src/main/proto/metrics.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman.metrics; 4 | 5 | option java_package = "org.coinen.pacman.metrics"; 6 | option java_outer_classname = "Metrics"; 7 | option java_multiple_files = true; 8 | 9 | message Meter { 10 | Id id = 1; 11 | repeated Measurement measure = 2; 12 | 13 | enum Type { 14 | COUNTER = 0; 15 | GAUGE = 1; 16 | LONG_TASK_TIMER = 2; 17 | TIMER = 3; 18 | DISTRIBUTION_SUMMARY = 4; 19 | OTHER = 5; 20 | } 21 | 22 | message Measurement { 23 | double value = 1; 24 | Statistic statistic = 2; 25 | } 26 | 27 | message Tag { 28 | string key = 1; 29 | string value = 2; 30 | } 31 | 32 | message Id { 33 | string name = 1; 34 | repeated Tag tag = 2; 35 | Type type = 3; 36 | Id synthetic_association = 4; 37 | string description = 5; 38 | string base_unit = 6; 39 | } 40 | 41 | enum Statistic { 42 | TOTAL = 0; 43 | TOTAL_TIME = 1; 44 | COUNT = 2; 45 | MAX = 3; 46 | VALUE = 4; 47 | UNKNOWN = 5; 48 | ACTIVE_TASKS = 6; 49 | DURATION = 7; 50 | } 51 | } 52 | 53 | message MetricsSnapshot { 54 | repeated Meter meters = 1; 55 | } 56 | -------------------------------------------------------------------------------- /metrics-idl/src/main/proto/service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.coinen.pacman.metrics; 4 | 5 | import "google/protobuf/empty.proto"; 6 | import "metrics.proto"; 7 | 8 | option java_package = "org.coinen.pacman.metrics"; 9 | option java_multiple_files = false; 10 | 11 | 12 | service MetricsSnapshotHandler { 13 | rpc StreamMetricsSnapshots (stream MetricsSnapshot) returns (google.protobuf.Empty) {} 14 | rpc SendMetricsSnapshot (MetricsSnapshot) returns (google.protobuf.Empty) {} 15 | } 16 | -------------------------------------------------------------------------------- /metrics-idl/src/main/resources/service_grpc_web_pb.d.ts.template: -------------------------------------------------------------------------------- 1 | import * as grpcWeb from 'grpc-web'; 2 | import { 3 | Empty, 4 | MetricsSnapshot} from './service_pb'; 5 | 6 | export class MetricsSnapshotHandlerClient { 7 | constructor (hostname: string, 8 | credentials: null | { [index: string]: string; }, 9 | options: null | { [index: string]: string; }); 10 | 11 | sendMetricsSnapshot( 12 | request: MetricsSnapshot, 13 | metadata: grpcWeb.Metadata, 14 | callback: (err: grpcWeb.Error, 15 | response: Empty) => void 16 | ): grpcWeb.ClientReadableStream; 17 | 18 | } 19 | 20 | export class MetricsSnapshotHandlerPromiseClient { 21 | constructor (hostname: string, 22 | credentials: null | { [index: string]: string; }, 23 | options: null | { [index: string]: string; }); 24 | 25 | sendMetricsSnapshot( 26 | request: MetricsSnapshot, 27 | metadata: grpcWeb.Metadata 28 | ): Promise; 29 | 30 | } -------------------------------------------------------------------------------- /metrics-idl/src/main/resources/service_rsocket_pb.d.ts.template: -------------------------------------------------------------------------------- 1 | import * as rsocket_flowable from 'rsocket-flowable'; 2 | import * as metrics_pb from './metrics_pb'; 3 | import { ReactiveSocket, Responder, Payload } from 'rsocket-types'; 4 | import {Empty} from "google-protobuf/google/protobuf/empty_pb"; 5 | 6 | 7 | export interface MetricsSnapshotHandler { 8 | streamMetricsSnapshots(message: rsocket_flowable.Flowable): rsocket_flowable.Single; 9 | streamMetricsSnapshots(message: rsocket_flowable.Flowable, metadata: Buffer): rsocket_flowable.Single; 10 | 11 | sendMetricsSnapshot(message: metrics_pb.MetricsSnapshot): rsocket_flowable.Single; 12 | sendMetricsSnapshot(message: metrics_pb.MetricsSnapshot, metadata: Buffer): rsocket_flowable.Single; 13 | } 14 | 15 | export class MetricsSnapshotHandlerClient implements MetricsSnapshotHandler { 16 | constructor(rs: ReactiveSocket); 17 | constructor(rs: ReactiveSocket, tracer: any); 18 | constructor(rs: ReactiveSocket, tracer: any, meterRegistry: any); 19 | 20 | 21 | streamMetricsSnapshots(message: rsocket_flowable.Flowable): rsocket_flowable.Single; 22 | streamMetricsSnapshots(message: rsocket_flowable.Flowable, metadata: Buffer): rsocket_flowable.Single; 23 | 24 | sendMetricsSnapshot(message: metrics_pb.MetricsSnapshot): rsocket_flowable.Single; 25 | sendMetricsSnapshot(message: metrics_pb.MetricsSnapshot, metadata: Buffer): rsocket_flowable.Single; 26 | } 27 | 28 | export class MetricsSnapshotHandlerServer implements Responder { 29 | constructor(service: MetricsSnapshotHandler); 30 | constructor(service: MetricsSnapshotHandler, tracer: any); 31 | constructor(service: MetricsSnapshotHandler, tracer: any, meterRegistry: any); 32 | 33 | fireAndForget(payload: Payload): void; 34 | requestResponse(payload: Payload): rsocket_flowable.Single>; 35 | requestStream(payload: Payload): rsocket_flowable.Flowable>; 36 | requestChannel(payloads: rsocket_flowable.Flowable>): rsocket_flowable.Flowable>; 37 | metadataPush(payload: Payload): rsocket_flowable.Single; 38 | } -------------------------------------------------------------------------------- /metrics-idl/src/main/typescript/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../../generated/main/javascript/metrics_pb'; 2 | 3 | import * as RSocketRPCServices from '../../generated/main/javascript/service_rsocket_pb'; 4 | import * as GRPCServices from '../../generated/main/javascript/service_grpc_web_pb'; 5 | 6 | export { 7 | GRPCServices, 8 | RSocketRPCServices 9 | } -------------------------------------------------------------------------------- /metrics-idl/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build", 4 | "noImplicitAny": true, 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "target": "es5", 8 | "downlevelIteration": true, 9 | "declaration": true, 10 | "typeRoots": [ 11 | "node_modules/@types", 12 | "src/@types/typescript" 13 | ], 14 | "lib": [ 15 | "es6", 16 | "scripthost", 17 | "dom" 18 | ], 19 | "baseUrl": "./", 20 | "rootDir": "./" 21 | }, 22 | "files": [ 23 | "src/main/typescript/index.ts" 24 | ], 25 | "include": [ 26 | "src/**/*" 27 | ] 28 | } -------------------------------------------------------------------------------- /metrics-server/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.springframework.boot' 2 | apply plugin: 'com.bmuschko.docker-spring-boot-application' 3 | 4 | dependencies { 5 | 6 | implementation project(':metrics-idl') 7 | implementation project(':metrics-client') 8 | implementation 'com.corundumstudio.socketio:netty-socketio' 9 | implementation 'io.grpc:grpc-stub' 10 | implementation 'io.grpc:grpc-okhttp' 11 | implementation 'io.grpc:grpc-netty' 12 | implementation 'io.grpc:grpc-protobuf' 13 | implementation 'com.salesforce.servicelibs:reactor-grpc-stub' 14 | implementation 'org.roaringbitmap:RoaringBitmap' 15 | implementation 'org.jctools:jctools-core' 16 | implementation 'io.rsocket:rsocket-core' 17 | implementation 'io.rsocket.rpc:rsocket-rpc-core' 18 | implementation 'com.google.protobuf:protobuf-java' 19 | implementation 'io.github.lognet:grpc-spring-boot-starter' 20 | implementation 'com.github.collaborationinencapsulation.spring-boot-rsocket:spring-boot-starter-rsocket' 21 | implementation 'org.springframework.boot:spring-boot-starter-freemarker' 22 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 23 | implementation 'org.springframework.boot:spring-boot-starter-webflux' 24 | implementation 'io.micrometer:micrometer-registry-influx' 25 | 26 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 27 | testImplementation 'io.projectreactor:reactor-test' 28 | } 29 | 30 | docker { 31 | springBootApplication { 32 | baseImage = 'adoptopenjdk/openjdk11' 33 | ports = [4000, 9095] 34 | mainClassName = 'org.coinen.reactive.pacman.metrics.ReactiveMetricsApplication' 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /metrics-server/src/main/java/org/coinen/reactive/pacman/metrics/ReactiveMetricsApplication.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ReactiveMetricsApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ReactiveMetricsApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /metrics-server/src/main/java/org/coinen/reactive/pacman/metrics/config/DefaultApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.config; 2 | 3 | import io.micrometer.influx.InfluxMeterRegistry; 4 | import org.coinen.reactive.pacman.metrics.service.support.FastInfluxMetricsBridgeService; 5 | import org.coinen.reactive.pacman.metrics.service.support.UnlimitedInfluxMetricsBridgeService; 6 | 7 | import org.springframework.beans.factory.annotation.Qualifier; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Primary; 11 | 12 | @Configuration 13 | public class DefaultApplicationConfig { 14 | 15 | @Bean 16 | @Primary 17 | public FastInfluxMetricsBridgeService influxMetricsBridgeService( 18 | InfluxMeterRegistry meterRegistry 19 | ) { 20 | return new FastInfluxMetricsBridgeService(meterRegistry); 21 | } 22 | 23 | @Bean 24 | @Qualifier("vip") 25 | public UnlimitedInfluxMetricsBridgeService unlimitedInfluxMetricsBridgeService( 26 | InfluxMeterRegistry meterRegistry 27 | ) { 28 | return new UnlimitedInfluxMetricsBridgeService(meterRegistry); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /metrics-server/src/main/java/org/coinen/reactive/pacman/metrics/controller/ConfigController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.controller; 2 | 3 | import org.coinen.reactive.pacman.metrics.service.support.FastInfluxMetricsBridgeService; 4 | 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | 10 | @Controller 11 | @RequestMapping("/config") 12 | public class ConfigController { 13 | 14 | final FastInfluxMetricsBridgeService fastInfluxMetricsBridgeService; 15 | 16 | public ConfigController(FastInfluxMetricsBridgeService service) { 17 | fastInfluxMetricsBridgeService = service; 18 | } 19 | 20 | @GetMapping 21 | public String configure( 22 | @RequestParam(value = "capacity", required = false) Long capacity, 23 | @RequestParam(value = "threshold", required = false) Long threshold) { 24 | fastInfluxMetricsBridgeService.configure(capacity, threshold); 25 | 26 | return "config"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /metrics-server/src/main/java/org/coinen/reactive/pacman/metrics/controller/grpc/GrpcMetricsController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.controller.grpc; 2 | 3 | import com.google.protobuf.Empty; 4 | import org.coinen.pacman.metrics.Meter; 5 | import org.coinen.pacman.metrics.MetricsSnapshot; 6 | import org.coinen.pacman.metrics.ReactorMetricsSnapshotHandlerGrpc; 7 | import org.coinen.reactive.pacman.metrics.MappingUtils; 8 | import org.coinen.reactive.pacman.metrics.service.MetricsService; 9 | import org.lognet.springboot.grpc.GRpcService; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | 13 | @GRpcService 14 | public class GrpcMetricsController extends 15 | ReactorMetricsSnapshotHandlerGrpc.MetricsSnapshotHandlerImplBase { 16 | 17 | final MetricsService metricsService; 18 | 19 | public GrpcMetricsController(MetricsService service) { 20 | metricsService = service; 21 | } 22 | 23 | @Override 24 | public Mono streamMetricsSnapshots(Flux metricsStream) { 25 | return metricsStream 26 | .hide() 27 | .concatMap(ms -> 28 | Flux.fromStream( 29 | ms.getMetersList() 30 | .stream() 31 | .filter(meter -> { 32 | Meter.Type type = meter.getId() 33 | .getType(); 34 | return type == Meter.Type.COUNTER || type == Meter.Type.TIMER; 35 | }) 36 | .map(MappingUtils::mapMeter)) 37 | ) 38 | .as(metricsService::metrics) 39 | .thenReturn(Empty.getDefaultInstance()); 40 | } 41 | 42 | @Override 43 | public Mono sendMetricsSnapshot(Mono messageMono) { 44 | return messageMono 45 | .doOnNext(message -> 46 | metricsService.metrics(Flux.fromStream(message.getMetersList().stream().map(MappingUtils::mapMeter))) 47 | ) 48 | .thenReturn(Empty.getDefaultInstance()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /metrics-server/src/main/java/org/coinen/reactive/pacman/metrics/controller/grpc/config/GrpcGameServerConfig.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.controller.grpc.config; 2 | 3 | import io.micrometer.influx.InfluxMeterRegistry; 4 | import org.coinen.reactive.pacman.metrics.grpc.ClientMetricsInterceptor; 5 | import org.coinen.reactive.pacman.metrics.grpc.ServerMetricsInterceptor; 6 | import org.lognet.springboot.grpc.GRpcGlobalInterceptor; 7 | 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class GrpcGameServerConfig { 13 | 14 | @Bean 15 | @GRpcGlobalInterceptor 16 | public ClientMetricsInterceptor clientLatencyInterceptor() { 17 | return new ClientMetricsInterceptor(); 18 | } 19 | 20 | @Bean 21 | @GRpcGlobalInterceptor 22 | public ServerMetricsInterceptor serverLatencyInterceptor(InfluxMeterRegistry registry) { 23 | return new ServerMetricsInterceptor(registry, "metrics"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /metrics-server/src/main/java/org/coinen/reactive/pacman/metrics/controller/http/HttpMetricsController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.controller.http; 2 | 3 | import com.google.protobuf.Empty; 4 | import io.micrometer.core.instrument.MeterRegistry; 5 | import org.coinen.pacman.metrics.MetricsSnapshot; 6 | import org.coinen.reactive.pacman.metrics.MappingUtils; 7 | import org.coinen.reactive.pacman.metrics.service.MetricsService; 8 | import reactor.core.publisher.Flux; 9 | 10 | import org.springframework.web.bind.annotation.CrossOrigin; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.RequestBody; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestMethod; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | @RestController 18 | @RequestMapping("/http") 19 | public class HttpMetricsController { 20 | 21 | final MetricsService metricsService; 22 | final MeterRegistry registry; 23 | 24 | public HttpMetricsController(MetricsService service, MeterRegistry registry) { 25 | this.metricsService = service; 26 | this.registry = registry; 27 | } 28 | 29 | @PostMapping("/metrics") 30 | @CrossOrigin(origins = "*", methods = RequestMethod.POST, allowedHeaders = "*", allowCredentials = "true") 31 | public Empty metrics(@RequestBody MetricsSnapshot metricsSnapshot) { 32 | // metricsService.metrics( 33 | // Flux.fromStream(metricsSnapshot.getMetersList() 34 | // .stream() 35 | // .map(MappingUtils::mapMeter)) 36 | // ); 37 | 38 | return Empty.getDefaultInstance(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /metrics-server/src/main/java/org/coinen/reactive/pacman/metrics/controller/rsocket/FilteredMetricsController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.controller.rsocket; 2 | 3 | import com.google.protobuf.Empty; 4 | import io.netty.buffer.ByteBuf; 5 | import org.coinen.pacman.metrics.Meter; 6 | import org.coinen.pacman.metrics.MetricsSnapshot; 7 | import org.coinen.pacman.metrics.MetricsSnapshotHandler; 8 | import org.coinen.reactive.pacman.metrics.MappingUtils; 9 | import org.coinen.reactive.pacman.metrics.service.MetricsService; 10 | import org.reactivestreams.Publisher; 11 | import reactor.core.publisher.Flux; 12 | import reactor.core.publisher.Mono; 13 | 14 | public class FilteredMetricsController implements MetricsSnapshotHandler { 15 | 16 | private final MetricsService metricsService; 17 | 18 | public FilteredMetricsController(MetricsService service) { 19 | metricsService = service; 20 | } 21 | 22 | @Override 23 | public Mono sendMetricsSnapshot(MetricsSnapshot message, ByteBuf metadata) { 24 | metricsService.metrics(Flux.fromStream(message.getMetersList().stream().map(MappingUtils::mapMeter))); 25 | 26 | return Mono.just(Empty.getDefaultInstance()); 27 | } 28 | 29 | @Override 30 | public Mono streamMetricsSnapshots(Publisher publisher, ByteBuf buf) { 31 | return Flux.from(publisher) 32 | .concatMap(ms -> 33 | Flux.fromStream( 34 | ms.getMetersList() 35 | .stream() 36 | .filter(meter -> { 37 | Meter.Type type = meter.getId() 38 | .getType(); 39 | return type == Meter.Type.COUNTER || type == Meter.Type.TIMER; 40 | }) 41 | .map(MappingUtils::mapMeter) 42 | ) 43 | ) 44 | .as(metricsService::metrics) 45 | .thenReturn(Empty.getDefaultInstance()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /metrics-server/src/main/java/org/coinen/reactive/pacman/metrics/controller/rsocket/MetricsController.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.controller.rsocket; 2 | 3 | import com.google.protobuf.Empty; 4 | import io.netty.buffer.ByteBuf; 5 | import org.coinen.pacman.metrics.Meter; 6 | import org.coinen.pacman.metrics.MetricsSnapshot; 7 | import org.coinen.pacman.metrics.MetricsSnapshotHandler; 8 | import org.coinen.reactive.pacman.metrics.MappingUtils; 9 | import org.coinen.reactive.pacman.metrics.service.MetricsService; 10 | import org.reactivestreams.Publisher; 11 | import reactor.core.publisher.Flux; 12 | import reactor.core.publisher.Mono; 13 | 14 | public class MetricsController implements MetricsSnapshotHandler { 15 | 16 | private final MetricsService metricsService; 17 | 18 | public MetricsController(MetricsService service) { 19 | metricsService = service; 20 | } 21 | 22 | @Override 23 | public Mono sendMetricsSnapshot(MetricsSnapshot message, ByteBuf metadata) { 24 | metricsService.metrics(Flux.fromStream(message.getMetersList().stream().map(MappingUtils::mapMeter))); 25 | 26 | return Mono.just(Empty.getDefaultInstance()); 27 | } 28 | 29 | @Override 30 | public Mono streamMetricsSnapshots(Publisher publisher, ByteBuf buf) { 31 | return Flux.from(publisher) 32 | .concatMap(ms -> 33 | Flux.fromStream( 34 | ms.getMetersList() 35 | .stream() 36 | .map(MappingUtils::mapMeter) 37 | ) 38 | ) 39 | .as(metricsService::metrics) 40 | .thenReturn(Empty.getDefaultInstance()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /metrics-server/src/main/java/org/coinen/reactive/pacman/metrics/service/MetricsService.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.service; 2 | 3 | import io.micrometer.core.instrument.Meter; 4 | import org.reactivestreams.Publisher; 5 | import reactor.core.publisher.Mono; 6 | 7 | public interface MetricsService { 8 | 9 | Mono metrics(Publisher messages); 10 | } 11 | -------------------------------------------------------------------------------- /metrics-server/src/main/java/org/coinen/reactive/pacman/metrics/service/support/RequestAwareSubscription.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.service.support; 2 | 3 | import org.reactivestreams.Subscription; 4 | import reactor.core.publisher.Operators; 5 | 6 | final class RequestAwareSubscription implements Subscription { 7 | final Subscription delegate; 8 | long requested = 0; 9 | long collected = 0; 10 | 11 | RequestAwareSubscription(Subscription delegate) { 12 | this.delegate = delegate; 13 | } 14 | 15 | @Override 16 | public void request(long n) { 17 | collected = Operators.addCap(collected, n); 18 | } 19 | 20 | @Override 21 | public void cancel() { 22 | delegate.cancel(); 23 | } 24 | 25 | public void tryFlush(long threshold, long received) { 26 | long pending = requested - received; 27 | 28 | if (pending > threshold) { 29 | return; 30 | } 31 | 32 | long n = Math.min(collected, threshold); 33 | 34 | if (n == 0) { 35 | return; 36 | } 37 | 38 | collected = 0; 39 | requested = Operators.addCap(requested, n); 40 | delegate.request(n); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /metrics-server/src/main/java/org/coinen/reactive/pacman/metrics/service/support/UnlimitedInfluxMetricsBridgeService.java: -------------------------------------------------------------------------------- 1 | package org.coinen.reactive.pacman.metrics.service.support; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.function.DoubleConsumer; 6 | 7 | import io.micrometer.core.instrument.Meter; 8 | import io.micrometer.core.instrument.MeterRegistry; 9 | import io.micrometer.influx.InfluxMeterRegistry; 10 | import org.coinen.reactive.pacman.metrics.service.MetricsService; 11 | import org.reactivestreams.Publisher; 12 | import org.reactivestreams.Subscriber; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import reactor.core.publisher.BaseSubscriber; 16 | import reactor.core.publisher.Flux; 17 | import reactor.core.publisher.Mono; 18 | import reactor.core.publisher.Operators; 19 | 20 | public class UnlimitedInfluxMetricsBridgeService implements MetricsService { 21 | 22 | private final InfluxMeterRegistry registry; 23 | private final ConcurrentHashMap consumers = new ConcurrentHashMap<>(); 24 | 25 | public UnlimitedInfluxMetricsBridgeService(InfluxMeterRegistry registry) { 26 | this.registry = registry; 27 | } 28 | 29 | @Override 30 | public Mono metrics(Publisher metersFlux) { 31 | return Flux 32 | .from(metersFlux) 33 | .transform(Operators.lift((__, s) -> new MeterBaseSubscriber( 34 | s, 35 | consumers, 36 | registry) 37 | )) 38 | .then(); 39 | } 40 | 41 | private static class MeterBaseSubscriber extends BaseSubscriber { 42 | 43 | private final Subscriber downstream; 44 | private final Map consumers; 45 | private final MeterRegistry registry; 46 | 47 | private MeterBaseSubscriber( 48 | Subscriber downstream, 49 | Map consumers, 50 | MeterRegistry registry) { 51 | this.downstream = downstream; 52 | this.consumers = consumers; 53 | this.registry = registry; 54 | } 55 | 56 | @Override 57 | protected void hookOnNext(Meter meter) { 58 | Recorder.record(meter, consumers, registry); 59 | } 60 | 61 | @Override 62 | protected void hookOnComplete() { 63 | downstream.onComplete(); 64 | } 65 | 66 | @Override 67 | protected void hookOnError(Throwable throwable) { 68 | downstream.onError(throwable); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /metrics-server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | grpc.port=9095 2 | management.metrics.export.influx.uri=http://localhost:8086 3 | rsocket.server.path=/ 4 | server.port=4000 5 | 6 | spring.freemarker.template-loader-path=classpath:/static/ 7 | spring.freemarker.suffix=.html 8 | -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/lib/bestPositionFinder.ts: -------------------------------------------------------------------------------- 1 | import store from '../store'; 2 | import distance from './distance'; 3 | 4 | function findBestStartingPosition(playerType: any) { 5 | let furthestDist = -1; 6 | let bestStart = null; 7 | let starts = store.getMaze().getTilePositions(); 8 | for (let i = 0; i < starts.length; i++) { 9 | let start = starts[i]; 10 | let closestPlayerDist = -1; 11 | Object.keys(store.getPlayers()).forEach(function (uuid: string, index) { 12 | let player = store.getPlayer(uuid); 13 | if (playerType != player.getType()) { 14 | let dist = distance(player.toObject().location.position, start); 15 | if (closestPlayerDist == -1 || dist < closestPlayerDist) { 16 | closestPlayerDist = dist; 17 | } 18 | } 19 | }); 20 | 21 | if (closestPlayerDist > furthestDist) { 22 | furthestDist = closestPlayerDist; 23 | bestStart = start; 24 | } 25 | } 26 | 27 | if (bestStart === null) { 28 | bestStart = starts[store.getMaze().getRandomIntInclusive(0, starts.length - 1)]; 29 | } 30 | 31 | return bestStart; 32 | } 33 | 34 | export default findBestStartingPosition; 35 | -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/lib/distance.ts: -------------------------------------------------------------------------------- 1 | import { Point } from '@shared/point_pb'; 2 | 3 | function distance(p1: Point.AsObject, p2: Point.AsObject): number { 4 | let d1 = p1.x - p2.x; 5 | let d2 = p1.y - p2.y; 6 | return d1 * d1 + d2 * d2; 7 | } 8 | 9 | export default distance; 10 | -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/lib/generatePlayerType.ts: -------------------------------------------------------------------------------- 1 | import { Player } from '@shared/player_pb'; 2 | import store from '../store'; 3 | 4 | function getNewPlayerType() { 5 | let manCount = 0; 6 | let ghostCount = 0; 7 | Object.keys(store.getPlayers).forEach(function (key:any, index:number) { 8 | if (store.getPlayer(key).getType() == Player.Type.PACMAN) { 9 | manCount++; 10 | } else if (store.getPlayer(key).getType() == Player.Type.GHOST) { 11 | ghostCount++; 12 | } 13 | }); 14 | 15 | if (ghostCount < manCount) { 16 | return Player.Type.GHOST; 17 | } else { 18 | return Player.Type.PACMAN; 19 | } 20 | } 21 | 22 | export default getNewPlayerType; 23 | -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/lib/processors/extrasProcessor.ts: -------------------------------------------------------------------------------- 1 | const { DirectProcessor } = require('reactor-core-js/flux'); 2 | 3 | const extrasProcessor = new DirectProcessor(); 4 | 5 | export default extrasProcessor; 6 | -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/lib/processors/index.ts: -------------------------------------------------------------------------------- 1 | import playersProcessor from './playersProcessor'; 2 | import extrasProcessor from './extrasProcessor'; 3 | 4 | export { 5 | playersProcessor, 6 | extrasProcessor 7 | } -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/lib/processors/playersProcessor.ts: -------------------------------------------------------------------------------- 1 | const { DirectProcessor } = require('reactor-core-js/flux'); 2 | 3 | const playersProcessor = new DirectProcessor(); 4 | 5 | export default playersProcessor; 6 | -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/lib/processors/scoreProcessor.ts: -------------------------------------------------------------------------------- 1 | const { DirectProcessor } = require('reactor-core-js/flux'); 2 | 3 | const scoreProcessor = new DirectProcessor(); 4 | 5 | export default scoreProcessor; 6 | -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/lib/services/extraService.ts: -------------------------------------------------------------------------------- 1 | 2 | import { extrasProcessor } from '../processors'; 3 | 4 | 5 | const extrasService = { 6 | extras() { 7 | return extrasProcessor; 8 | } 9 | }; 10 | 11 | export default extrasService; 12 | -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/lib/services/gameService.ts: -------------------------------------------------------------------------------- 1 | const { DirectProcessor } = require('reactor-core-js/flux'); 2 | 3 | import { Single } from 'rsocket-flowable'; 4 | 5 | import { Nickname } from '@shared/player_pb'; 6 | import { Config } from '@shared/config_pb'; 7 | import { Player } from '@shared/player_pb'; 8 | import { Location, Direction } from '@shared/location_pb'; 9 | import { Point } from '@shared/point_pb'; 10 | import getNewPlayerType from '../generatePlayerType'; 11 | import store from '../../store'; 12 | import { playersProcessor } from '../processors'; 13 | import findBestStartingPosition from '../bestPositionFinder'; 14 | 15 | const gameService = { 16 | start(nickname: Nickname, uuid: string) { 17 | if (nickname.getValue().length <= 13 && !store.getPlayer(uuid)) { 18 | let name = nickname.getValue().replace(/[^a-zA-Z0-9. ]/g, ''); 19 | if (name == "") { 20 | name = "Unnamed"; 21 | } 22 | const score = 10; 23 | const playerType = getNewPlayerType(); 24 | const pos = findBestStartingPosition(playerType); 25 | const config = new Config(); 26 | const player = new Player(); 27 | const playerPosition = new Point(); 28 | const location = new Location(); 29 | 30 | 31 | playerPosition.setX(pos.x); 32 | playerPosition.setY(pos.y); 33 | 34 | location.setDirection(Direction.RIGHT); 35 | location.setPosition(playerPosition); 36 | 37 | player.setLocation(location); 38 | player.setNickname(name); 39 | player.setState(Player.State.CONNECTED); 40 | player.setScore(score); 41 | player.setType(playerType); 42 | player.setUuid(uuid); 43 | 44 | 45 | config.setPlayersList(Object.keys(store.getPlayers()).map(k => store.getPlayer(k))) 46 | config.setPlayer(player); 47 | config.setExtrasList(store.getMaze().extras.toArray()); 48 | config.setScoresList(store.getLeaderBoard()); 49 | playersProcessor.onNext(player); 50 | 51 | store.setPlayer(player); 52 | 53 | return Single.of(config); 54 | } 55 | 56 | return Single.error(new Error("wrong name")); 57 | } 58 | }; 59 | 60 | export default gameService; 61 | -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/lib/services/index.ts: -------------------------------------------------------------------------------- 1 | import extrasService from './extraService'; 2 | import gameService from './gameService'; 3 | import playerService from './playerService'; 4 | 5 | export { 6 | extrasService, 7 | gameService, 8 | playerService 9 | } -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/lib/services/playerService.ts: -------------------------------------------------------------------------------- 1 | import { PlayerServiceServer } from '@shared/service_rsocket_pb'; 2 | 3 | import { Player } from '@shared/player_pb'; 4 | import { Location } from '@shared/location_pb'; 5 | import { Extra } from '@shared/extra_pb'; 6 | import { playersProcessor, extrasProcessor } from '../processors'; 7 | import store from '../../store'; 8 | import { Flowable } from 'rsocket-flowable'; 9 | // import scoreProcessor from '../processors/scoreProcessor'; 10 | 11 | const playerService = { 12 | locate(locationStream: Flowable, uuid: string) { 13 | return locationStream.map(location => { 14 | const time = Date.now(); 15 | const player = store.getPlayer(uuid); 16 | 17 | player.setTimestamp(time); 18 | player.setState(Player.State.ACTIVE); 19 | player.setLocation(location); 20 | 21 | const collisionData = store.getMaze().collideFood(location.getPosition().getX(), location.getPosition().getY()); 22 | if (collisionData) { 23 | if (Math.sign(collisionData) === 1) { 24 | player.setScore(player.getScore() + 1); 25 | // scoreProcessor.onNext({player, score: player.getScore() + 1}); 26 | } else { 27 | let sec = 10; 28 | store.setPowerUpEnd(Date.now() + sec * 1000); 29 | } 30 | let addedFood = store.getMaze().addFood(Object.keys(store.getPlayers()).length); 31 | 32 | const extra = new Extra(); 33 | 34 | extra.setLast(collisionData); 35 | extra.setCurrent(addedFood); 36 | 37 | extrasProcessor.onNext(extra); 38 | 39 | console.log("Sent: " + extra.toObject()) 40 | } 41 | playersProcessor.onNext(player); 42 | }) 43 | }, 44 | 45 | players() { 46 | return playersProcessor; 47 | } 48 | 49 | } 50 | 51 | export default playerService; 52 | -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/lib/services/scoreService.ts: -------------------------------------------------------------------------------- 1 | import { ExtrasServiceServer } from '@shared/service_rsocket_pb'; 2 | import { Player } from '@shared/player_pb'; 3 | import scoreProcessor from '../processors/scoreProcessor'; 4 | 5 | const ScoreService = new ExtrasServiceServer({ 6 | score(player: Player) { 7 | return scoreProcessor ; 8 | } 9 | 10 | }); 11 | 12 | export default ScoreService; 13 | -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/store.ts: -------------------------------------------------------------------------------- 1 | import { Player } from '@shared/player_pb'; 2 | import { Score } from '@shared/score_pb'; 3 | import Maze from './maze'; 4 | 5 | const store = { 6 | players: <{ [e: string]: Player }> null, 7 | maze: null, 8 | powerUpEnd: 0, 9 | leaderBoard: Array(), 10 | leaderBoardIndexes: new Map() 11 | } 12 | 13 | 14 | const swapWithPreceeding = (index: integer) => { 15 | let temp = store.leaderBoard[index]; 16 | store.leaderBoard[index] = store.leaderBoard[index - 1]; 17 | store.leaderBoard[index - 1] = temp; 18 | store.leaderBoardIndexes.set(store.leaderBoard[index].getUsername(), index); 19 | store.leaderBoardIndexes.set(store.leaderBoard[index - 1].getUsername(), index - 1); 20 | } 21 | 22 | const incrementLeaderboard = (index: integer, val: integer) => { 23 | for(let i = index; i > 1; i--) { 24 | if (store.leaderBoard[i] > store.leaderBoard[i - 1]) { 25 | swapWithPreceeding(i); 26 | } else { 27 | break; 28 | } 29 | } 30 | } 31 | 32 | const accessors = { 33 | getPlayers: () => store.players, 34 | getPlayer: (uuid: string) => store.players[uuid], 35 | getMaze: () => store.maze, 36 | getPowerUpEnd: () => store.powerUpEnd, 37 | getLeaderBoard: () => store.leaderBoard, 38 | getLeaderBoardIndex: (player: String) => store.leaderBoardIndexes.get(player), 39 | getScore: (player: String) => store.leaderBoard[store.leaderBoardIndexes.get(player)], 40 | setPlayers: (value: { [e: string]: Player }) => store.players = value, 41 | setPlayer: (value: Player) => store.players[value.getUuid()] = value, 42 | setMaze: (value: Maze) => store.maze = value, 43 | setPowerUpEnd: (value: integer) => store.powerUpEnd = value, 44 | incrementScore: (player: String, value: integer) => { 45 | let index = this.getLeaderBoardIndex(player); 46 | incrementLeaderboard(index, value); 47 | } 48 | } 49 | 50 | 51 | 52 | export default accessors; 53 | -------------------------------------------------------------------------------- /metrics-server/src/main/typescript/tile.ts: -------------------------------------------------------------------------------- 1 | export default class Tile { 2 | constructor(public x: number, public y: number) { 3 | } 4 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pacman", 3 | "version": "0.0.1", 4 | "description": "", 5 | "scripts": { 6 | "postinstall": "lerna bootstrap" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "devDependencies": { 11 | "@types/uuid": "^7.0.3", 12 | "follow-redirects": "^1.11.0", 13 | "lerna": "^3.20.2" 14 | }, 15 | "dependencies": { 16 | "@types/cookie-parser": "^1.4.2", 17 | "cookie-parser": "^1.4.5", 18 | "uuid": "^7.0.3" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /scripts/copy-template.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | // destination.txt will be created or overwritten by default. 4 | fs.copyFile(process.argv[2], process.argv[3], (err) => { 5 | if (err) throw err; 6 | console.log(`${process.argv[2]} was copied to ${process.argv[3]}`); 7 | }); -------------------------------------------------------------------------------- /scripts/grpc-web-plugin-loader.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const https = require('follow-redirects/https'); 3 | const fs = require('fs'); 4 | 5 | const platform = os.platform(); 6 | const gRpcWebVersion = '1.0.5'; 7 | const baseLink = `https://github.com/grpc/grpc-web/releases/download/${gRpcWebVersion}/protoc-gen-grpc-web`; 8 | const arch = 'x86_64'; 9 | const destinationFile = "./node_modules/.bin/protoc-gen-grpc-web.exe"; 10 | 11 | let platformName; 12 | 13 | switch (platform) { 14 | case "win32": 15 | platformName = 'windows'; 16 | break; 17 | case "darwin": 18 | platformName = 'darwin'; 19 | break; 20 | default: 21 | platformName = 'linux'; 22 | } 23 | 24 | 25 | const file = fs.createWriteStream(destinationFile); 26 | https.get( 27 | `${baseLink}-${gRpcWebVersion}-${platformName}-${arch}${platform === 'win32' ? '.exe' : ''}`, 28 | (response) => { 29 | response.pipe(file); 30 | file.on('finish', () => { 31 | file.close(); 32 | fs.chmodSync(destinationFile, '755'); 33 | console.log("File has been downloaded"); 34 | }); 35 | }) 36 | .on('error', (e) => { 37 | fs.unlink(destinationFile); 38 | console.error("Download failed. ", e); 39 | }); 40 | 41 | -------------------------------------------------------------------------------- /scripts/make-local-generated-dir.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | fs.mkdir('./src/generated/main/javascript', { recursive: true }, (err) => { 4 | if (err) throw err; 5 | }); -------------------------------------------------------------------------------- /scripts/postprocess-generated-ts.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const fileToReplaceExports = fs.readFileSync(process.argv[2], "utf8"); 4 | 5 | fs.writeFileSync( 6 | process.argv[2], 7 | fileToReplaceExports.replace(/^import \* as (?!jspb).* from/gm, "export * from"), 8 | "utf8" 9 | ); 10 | 11 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { url 'https://repo.spring.io/snapshot' } 4 | maven { url 'https://repo.spring.io/milestone' } 5 | gradlePluginPortal() 6 | } 7 | resolutionStrategy { 8 | eachPlugin { 9 | if(requested.id.id == 'org.springframework.boot') { 10 | useModule("org.springframework.boot:spring-boot-gradle-plugin:${requested.version}") 11 | } 12 | } 13 | } 14 | } 15 | rootProject.name = 'reactive-pacman' 16 | 17 | include 'game-idl' 18 | include 'game-server' 19 | include 'game-client' 20 | include 'metrics-idl' 21 | include 'metrics-server' 22 | include 'metrics-client' 23 | --------------------------------------------------------------------------------