Compare commits

...

8 Commits

Author SHA1 Message Date
5d71a1b19b wip 2022-11-10 21:24:49 +01:00
c17bb904af wip graphql split api
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2022-08-07 17:43:40 +02:00
f982af16c2 Add ADR documentation
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2022-08-07 11:50:01 +02:00
7568e38575 start fixing specs
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/pr Build is failing
2022-08-07 11:39:37 +02:00
3aedc4a8ff Update README 2022-08-07 11:37:17 +02:00
bb5e57e8b2 wip add drone
Some checks failed
continuous-integration/drone Build is failing
2022-08-07 11:28:19 +02:00
6bc31f3079 wip 2022-08-07 11:24:17 +02:00
4cae521faf
wip 2022-08-06 21:44:13 +02:00
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 # 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) [![Build Status](https://drone.sceptique.eu/api/badges/TETU/Core/status.svg)](https://drone.sceptique.eu/TETU/Core)
@ -11,24 +11,20 @@ Install `git`, `sfml`, `crystal`, `make`, `imgui` (`imgui-sfml` with archlinux).
# install dependencies first # install dependencies first
make deps make deps
# make with imgui static linking (I think, I don't remember) # make an optimised build so it's fasteeer
export LD_LIBRARY_PATH="$(pwd)/cimgui"
make release make release
## Usage ## Usage
# run the ./core # run the server (high performance for data)
export LD_LIBRARY_PATH="$(pwd)/lib/imgui-sfml" xdg-open https://localhost:3000 # open the game UI (browser UI for simplicity)
./core
# there is also a make rule that handle the libraries
make run
## Development ## Development
* See the wiki <https://git.sceptique.eu/TETU/Core/wiki>. * See the wiki for documentation: <https://git.sceptique.eu/TETU/Core/wiki>.
* See the current kanban: <https://git.sceptique.eu/TETU/Core/projects> * See the current kanban for current WIP: <https://git.sceptique.eu/TETU/Core/projects>.
* Come talk on IRC **irc://irc.sceptique.eu#TETU** * Come talk on IRC: **irc://irc.sceptique.eu:6697#TETU**.
## Contributing ## Contributing
@ -40,7 +36,7 @@ Install `git`, `sfml`, `crystal`, `make`, `imgui` (`imgui-sfml` with archlinux).
## Contributors ## Contributors
- [Arthur Poulet](https://git.sceptique.eu/Sceptique) - creator and maintainer - [Arthur Poulet](https://git.sceptique.eu/Sceptique) - creator and maintainer.
## Particular mentions ## 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 version: 2.0
shards: shards:
crsfml: backtracer:
git: https://github.com/oprypin/crsfml.git git: https://github.com/sija/backtracer.cr.git
version: 2.5.2 version: 1.2.1
entitas: entitas:
git: https://github.com/spoved/entitas.cr.git git: https://github.com/spoved/entitas.cr.git
version: 1.4.5 version: 1.4.5
imgui: exception_page:
git: https://github.com/oprypin/crystal-imgui.git git: https://github.com/crystal-loot/exception_page.git
version: 1.87 version: 0.2.2
imgui-sfml: graphql:
git: https://github.com/oprypin/crystal-imgui-sfml.git git: https://github.com/graphql-crystal/graphql.git
version: 1.87 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: spoved:
git: https://github.com/spoved/spoved.cr.git git: https://github.com/spoved/spoved.cr.git

View File

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

View File

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

View File

@ -1,33 +1,33 @@
require "./configuration" require "./configuration"
class TETU::Window # class TETU::Window
@@instance = Window.new # @@instance = Window.new
def self.instance # def self.instance
@@instance # @@instance
end # end
GALAXY_WIDTH = TETU::MAX_X # GALAXY_WIDTH = TETU::MAX_X
GALAXY_HEIGHT = TETU::MAX_Y # GALAXY_HEIGHT = TETU::MAX_Y
UI_WIDTH = GALAXY_WIDTH + TETU::UI_CONF["right_sidebar"].as_i64 # UI_WIDTH = GALAXY_WIDTH + TETU::UI_CONF["right_sidebar"].as_i64
UI_HEIGHT = GALAXY_HEIGHT # UI_HEIGHT = GALAXY_HEIGHT
SQUARE_SIZE = TETU::UI_CONF["square_size"].as_i64 # 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 window : SF::RenderWindow
getter delta_clock : SF::Clock # getter delta_clock : SF::Clock
property planet_menu_selected : GameEntity? = nil # property planet_menu_selected : GameEntity? = nil
def initialize # def initialize
@window = SF::RenderWindow.new( # @window = SF::RenderWindow.new(
SF::VideoMode.new(UI_WIDTH, UI_HEIGHT), # SF::VideoMode.new(UI_WIDTH, UI_HEIGHT),
"To the End of The Universe", # "To the End of The Universe",
) # )
@delta_clock = SF::Clock.new # @delta_clock = SF::Clock.new
end # end
def [](k) # def [](k)
@data[k] # @data[k]
end # end
end # end

View File

@ -1,2 +1,2 @@
require "./systems/*" 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