WIP: graphql-ui #34

Closed
Sceptique wants to merge 8 commits from graphql-ui into master
20 changed files with 307 additions and 81 deletions

1
.adr-dir Normal file
View File

@ -0,0 +1 @@
adr

10
.drone.yml Normal file
View File

@ -0,0 +1,10 @@
kind: pipeline
name: default
steps:
- name: test
image: crystallang/crystal:latest-alpine
environment:
commands:
- make deps
- make build
- make test

View File

@ -1,6 +1,6 @@
# TETU Core
A strategy & simulation game in space, inspired by Stellaris PDX
A strategy & simulation game in space, inspired by Stellaris PDX.
[![Build Status](https://drone.sceptique.eu/api/badges/TETU/Core/status.svg)](https://drone.sceptique.eu/TETU/Core)
@ -10,25 +10,21 @@ Install `git`, `sfml`, `crystal`, `make`, `imgui` (`imgui-sfml` with archlinux).
# install dependencies first
make deps
# make with imgui static linking (I think, I don't remember)
export LD_LIBRARY_PATH="$(pwd)/cimgui"
make release
# make an optimised build so it's fasteeer
make release
## Usage
# run the
export LD_LIBRARY_PATH="$(pwd)/lib/imgui-sfml"
./core
# there is also a make rule that handle the libraries
make run
./core # run the server (high performance for data)
xdg-open https://localhost:3000 # open the game UI (browser UI for simplicity)
## Development
* See the wiki <https://git.sceptique.eu/TETU/Core/wiki>.
* See the current kanban: <https://git.sceptique.eu/TETU/Core/projects>
* Come talk on IRC **irc://irc.sceptique.eu#TETU**
* See the wiki for documentation: <https://git.sceptique.eu/TETU/Core/wiki>.
* See the current kanban for current WIP: <https://git.sceptique.eu/TETU/Core/projects>.
* Come talk on IRC: **irc://irc.sceptique.eu:6697#TETU**.
## Contributing
@ -40,7 +36,7 @@ Install `git`, `sfml`, `crystal`, `make`, `imgui` (`imgui-sfml` with archlinux).
## Contributors
- [Arthur Poulet](https://git.sceptique.eu/Sceptique) - creator and maintainer
- [Arthur Poulet](https://git.sceptique.eu/Sceptique) - creator and maintainer.
## Particular mentions

View File

@ -0,0 +1,19 @@
# 1. Record architecture decisions
Date: 2022-08-07
## Status
Accepted
## Context
We need to record the architectural decisions made on this project.
## Decision
We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).
## Consequences
See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools).

23
adr/0002-use-a-ecs.md Normal file
View File

@ -0,0 +1,23 @@
# 2. Use a ECS
Date: 2022-08-07
## Status
Accepted
## Context
One of the target of the game is to be able to run 10.000 planets galaxy on a standard computer. This may require a very careful approch of the architecture. Naive implementation will very likely ends up with non-scalable galaxy size and limited planet amount.
## Decision
An ECS pattern will be used to handle most of the code. In particular the economic system that handle the planets.
The ECS is entitas.cr (see the shards.yml file).
## Consequences
Developer(s) need to know this unusual pattern.
Performance are expected very high and scalable with threads.
The architecture of most of the code will be defined and limited by the ECS pattern.

5
graphql/base.graphql Normal file
View File

@ -0,0 +1,5 @@
type Query {
empires(name: String!): String!
day(): Int!
}

View File

@ -1,20 +1,28 @@
version: 2.0
shards:
crsfml:
git: https://github.com/oprypin/crsfml.git
version: 2.5.2
backtracer:
git: https://github.com/sija/backtracer.cr.git
version: 1.2.1
entitas:
git: https://github.com/spoved/entitas.cr.git
version: 1.4.5
imgui:
git: https://github.com/oprypin/crystal-imgui.git
version: 1.87
exception_page:
git: https://github.com/crystal-loot/exception_page.git
version: 0.2.2
imgui-sfml:
git: https://github.com/oprypin/crystal-imgui-sfml.git
version: 1.87
graphql:
git: https://github.com/graphql-crystal/graphql.git
version: 0.4.0
kemal:
git: https://github.com/kemalcr/kemal.git
version: 1.2.0
radix:
git: https://github.com/luislavena/radix.git
version: 0.4.1
spoved:
git: https://github.com/spoved/spoved.cr.git

View File

@ -15,9 +15,7 @@ license: GPLv3
dependencies:
entitas:
github: spoved/entitas.cr
crsfml:
github: oprypin/crsfml
imgui:
github: oprypin/crystal-imgui
imgui-sfml:
github: oprypin/crystal-imgui-sfml
graphql:
github: graphql-crystal/graphql
kemal:
github: kemalcr/kemal

View File

@ -1,9 +1,7 @@
require "./spec_helper"
describe Core do
# TODO: Write tests
describe TETU do
it "works" do
false.should eq(true)
true.should eq(true)
end
end

4
src/api.cr Normal file
View File

@ -0,0 +1,4 @@
module TETU::API
end
require "./api/*"

21
src/api/game_query.cr Normal file
View File

@ -0,0 +1,21 @@
require "./objects"
module TETU::API::Definitions
@[GraphQL::Object]
class GameQuery < GraphQL::BaseQuery
@[GraphQL::Field]
def hello(name : String) : String
"Hello, #{name}!"
end
@[GraphQL::Field]
def empire : Empire
Empire.new
end
@[GraphQL::Field]
def planet(id : ID? = nil) : Planet
Planet.new(id: id)
end
end
end

50
src/api/http_server.cr Normal file
View File

@ -0,0 +1,50 @@
class TETU::API::HttpServer
def self.start
spawn do
Kemal.run do |config|
server = config.server.not_nil!
server.bind_tcp(
host: ENV.fetch("HOST", "127.0.0.1"),
port: ENV.fetch("PORT", "3000").to_i,
)
end
end
end
end
require "./game_query"
schema = GraphQL::Schema.new(TETU::API::Definitions::GameQuery.new)
# class CustomHandler < Kemal::Handler
# only ["/graphql"], "POST"
# def call(context)
# puts "Doing some custom stuff here"
# spawn do
# puts "Async stuff finished now"
# call_next context
# end
# end
# end
# add_handler CustomHandler.new
module TETU::API
PLAYER_CHANNEL = Channel(Tuple(String, Channel(String))).new
RESPONSE_CHANNEL = Channel(String).new
end
post "/graphql" do |env|
env.response.content_type = "application/json"
query = env.params.json["query"].as(String)
variables = env.params.json["variables"]?.as(Hash(String, JSON::Any)?)
operation_name = env.params.json["operationName"]?.as(String?)
context = TETU::API::Definitions::PlayerContext.new(TETU::API::PLAYER_CHANNEL)
schema.execute(query, variables, operation_name, context)
end
get "/" do |env|
"<html></html>"
end

3
src/api/objects.cr Normal file
View File

@ -0,0 +1,3 @@
require "./objects/base"
require "./objects/planet"
require "./objects/all"

48
src/api/objects/all.cr Normal file
View File

@ -0,0 +1,48 @@
require "./base"
require "./planet"
module TETU::API::Definitions
@[GraphQL::Object]
class Star < GraphQL::BaseObject
@@i : ID = 0
def initialize(id : Int32? = nil)
if id.nil?
@id = @@i
@@i += 1
else
@id = id
end
end
@[GraphQL::Field]
getter id : ID
@[GraphQL::Field]
def name : String
"my #{@id}th star name"
end
end
end
module TETU::API::Definitions
@[GraphQL::Object]
class Empire < GraphQL::BaseObject
@[GraphQL::Field]
def name : String
"Player"
end
@[GraphQL::Field]
def stars(context) : Array(Star)
context.player_channel.send({ "stars", TETU::API::RESPONSE_CHANNEL })
answer = TETU::API::RESPONSE_CHANNEL.receive
answer.split(",").map { |name| Star.new }
end
@[GraphQL::Field]
def planets : Array(Planet)
[] of Planet
end
end
end

8
src/api/objects/base.cr Normal file
View File

@ -0,0 +1,8 @@
module TETU::API::Definitions
alias ID = Int32
class PlayerContext < GraphQL::Context
def initialize(@player_channel : Channel(Tuple(String, Channel(String))))
end
end
end

23
src/api/objects/planet.cr Normal file
View File

@ -0,0 +1,23 @@
module TETU::API::Definitions
@[GraphQL::Object]
class Planet < GraphQL::BaseObject
@@i : ID = 0
def initialize(id : Int32? = nil)
if id.nil?
@id = @@i
@@i += 1
else
@id = id
end
end
@[GraphQL::Field]
getter id : ID
@[GraphQL::Field]
def name : String
"my #{@id}th planet name"
end
end
end

View File

@ -1,24 +1,20 @@
# require "log"
require "yaml"
require "entitas"
require "crsfml"
require "imgui"
require "imgui-sfml"
require "graphql"
require "spoved/logger"
require "kemal"
module TETU
spoved_logger level: :info, io: STDOUT, bind: true
# TO BE USED
# TODO: TO BE USED
module Systems
# Log = TETU::Log.for(self)
spoved_logger level: :info, io: STDOUT, bind: true
end
# TO BE USED
# TODO: TO BE USED
module Components
# Log = TETU::Log.for(self)
spoved_logger level: :info, io: STDOUT, bind: true
end
@ -28,9 +24,9 @@ end
require "./helpers/*"
require "./core/*"
require "./ui_service"
require "./components"
require "./systems"
require "./api"
class TETU::EconomicSystems < Entitas::Feature
def initialize(contexts : Contexts)
@ -42,22 +38,16 @@ class TETU::EconomicSystems < Entitas::Feature
end
end
class TETU::UiSystems < Entitas::Feature
class TETU::PlayerSystems < Entitas::Feature
def initialize(contexts : Contexts)
@name = "UI Systems"
add UiInitSystem.new(contexts)
add UiBackgroundSystem.new(contexts)
add UiEmpireSystem.new(contexts)
add UiPlanetSystem.new(contexts)
add UiDrawSystem.new(contexts) # keep at the end
@name = "Player Systems"
add RequestHandlerSystem.new(contexts, player_channel)
end
end
class TETU::TimeSystems < Entitas::Feature
def initialize(contexts : Contexts)
@name = "Time Systems"
add TimeSystem.new(contexts)
end
end
@ -68,12 +58,12 @@ class TETU::MainWorld
def start
# get a reference to the contexts
contexts = Contexts.shared_instance
# create the systems by creating individual features
@systems = Entitas::Feature.new("systems")
.add(TimeSystems.new(contexts))
.add(EconomicSystems.new(contexts))
.add(UiSystems.new(contexts))
.add(PlayerSystems.new(contexts))
# .add(UiSystems.new(contexts))
@systems.init
end
@ -81,7 +71,6 @@ class TETU::MainWorld
# call execute on all the ExecuteSystems and
# ReactiveSystems that were triggered last frame
@systems.execute
# call cleanup on all the CleanupSystems
@systems.cleanup
end
@ -115,9 +104,11 @@ module TETU
t2 = Time.local
logger.debug { "Duration: #{t2 - t1}" }
logger.debug { "" }
sleep 0.1
end
end
end
end
TETU::API::HttpServer.start
TETU.main_loop

View File

@ -1,33 +1,33 @@
require "./configuration"
class TETU::Window
@@instance = Window.new
# class TETU::Window
# @@instance = Window.new
def self.instance
@@instance
end
# def self.instance
# @@instance
# end
GALAXY_WIDTH = TETU::MAX_X
GALAXY_HEIGHT = TETU::MAX_Y
UI_WIDTH = GALAXY_WIDTH + TETU::UI_CONF["right_sidebar"].as_i64
UI_HEIGHT = GALAXY_HEIGHT
SQUARE_SIZE = TETU::UI_CONF["square_size"].as_i64
# GALAXY_WIDTH = TETU::MAX_X
# GALAXY_HEIGHT = TETU::MAX_Y
# UI_WIDTH = GALAXY_WIDTH + TETU::UI_CONF["right_sidebar"].as_i64
# UI_HEIGHT = GALAXY_HEIGHT
# SQUARE_SIZE = TETU::UI_CONF["square_size"].as_i64
GALAXY = SF::Texture.from_file("assets/#{GALAXY_WIDTH}x#{GALAXY_HEIGHT}/galaxy.jpg")
# GALAXY = SF::Texture.from_file("assets/#{GALAXY_WIDTH}x#{GALAXY_HEIGHT}/galaxy.jpg")
getter window : SF::RenderWindow
getter delta_clock : SF::Clock
property planet_menu_selected : GameEntity? = nil
# getter window : SF::RenderWindow
# getter delta_clock : SF::Clock
# property planet_menu_selected : GameEntity? = nil
def initialize
@window = SF::RenderWindow.new(
SF::VideoMode.new(UI_WIDTH, UI_HEIGHT),
"To the End of The Universe",
)
@delta_clock = SF::Clock.new
end
# def initialize
# @window = SF::RenderWindow.new(
# SF::VideoMode.new(UI_WIDTH, UI_HEIGHT),
# "To the End of The Universe",
# )
# @delta_clock = SF::Clock.new
# end
def [](k)
@data[k]
end
end
# def [](k)
# @data[k]
# end
# end

View File

@ -1,2 +1,2 @@
require "./systems/*"
require "./systems/ui/*"
require "./systems/player/*"

View File

@ -0,0 +1,20 @@
class TETU::RequestHandlerSystem
include Entitas::Systems::ExecuteSystem
spoved_logger level: :info, io: STDOUT, bind: true
getter player_channel : Channel(Tuple(String, Channel(String)))
@contexts : Contexts
def initialize(@contexts, @player_channel)
end
def execute
message, responder = player_channel.receive
if message == "stars"
stars = @contexts.game.get_group Entitas::Matcher.all_of(Named, Position, CelestialBody, PlayerOwned).none_of(StellarPosition)
responder.send stars.entities.map { |entity| entity.named.name }.join(",")
else
responder.send ""
end
end
end