Slight improvement of the work of nitcc.
Numbers with raw parsing of a 30MB json file from nitrpg (only the AST creation, no further analysis)
Before: 0m5.700s ; 25.1 GIr
After: 0m3.540s (-38%) ; 20.3 GIr (-19%)
There is still a lot (99?) of improvements to make before having reasonable numbers, but the nitcc_runtime part ain't one.
Pull-Request: #1876
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
# limitations under the License.
NITC=../../bin/nitc
+NITU=../../bin/nitunit
all: listener web
web:
$(NITC) src/web.nit
+check:
+ $(NITU) src/game.nit
+ $(NITU) src/events.nit
+ $(NITU) src/statistics.nit
+ $(NITU) src/achievements.nit
+ $(NITU) src/listener.nit
+
clean:
rm listener web
#
# TODO should update the achievement?
fun add_achievement(achievement: Achievement) do
- var key = self.key / achievement.key
- if game.store.has_key(key) then return
stats.inc("achievements")
- achievement.save_in(self.key)
- save
+ achievement.owner = self
+ achievement.save
end
+ # Is `a` unlocked for this `Player`?
+ fun has_achievement(a: Achievement): Bool do return load_achievement(a.id) != null
+
# Load the event from its `id`.
#
# Looks for the event save file in game data.
# Returns `null` if the event cannot be found.
fun load_achievement(id: String): nullable Achievement do
- var key = self.key / "achievements" / id
- if not game.store.has_key(key) then return null
- var json = game.store.load_object(key)
- return new Achievement.from_json(game, json)
+ var req = new JsonObject
+ req["id"] = id
+ req["game"] = game.key
+ req["owner"] = key
+ var obj = game.db.collection("achievements").find(req)
+ if obj isa JsonObject then
+ return new Achievement.from_json(game, obj)
+ end
+ return null
end
# List all events registered in this entity.
#
# To add events see `add_event`.
fun load_achievements: MapRead[String, Achievement] do
+ var req = new JsonObject
+ req["game"] = game.key
+ req["owner"] = key
var res = new HashMap[String, Achievement]
- var key = self.key / "achievements"
- if not game.store.has_collection(key) then return res
- var coll = game.store.list_collection(key)
- for id in coll do
- res[id.to_s] = load_achievement(id.to_s).as(not null)
+ for obj in game.db.collection("achievements").find_all(req) do
+ var achievement = new Achievement.from_json(game, obj)
+ res[achievement.id] = achievement
end
return res
end
class Achievement
super GameEntity
+ redef var collection_name = "achievements"
redef var game
- redef var key is lazy do
+ redef fun key do
var owner = self.owner
if owner == null then return id
return "{owner.key}-{id}"
#
# Used to load achievements from storage.
init from_json(game: Game, json: JsonObject) do
- init(game, json["id"].to_s, json["name"].to_s, json["desc"].to_s, json["reward"].as(Int))
+ init(game,
+ json["id"].as(String),
+ json["name"].as(String),
+ json["desc"].as(String),
+ json["reward"].as(Int))
end
redef fun to_json do
json["desc"] = desc
json["reward"] = reward
json["game"] = game.key
+ var owner = self.owner
if owner != null then json["owner"] = owner.key
return json
end
end
redef class Player
-
- # Is `a` unlocked for this `Player`?
- fun has_achievement(a: Achievement): Bool do
- return load_achievement(a.id) != null
- end
-
# Unlocks an achievement for this Player based on a GithubEvent.
#
# Register the achievement and adds the achievement reward to the player
nitcoins += a.reward
add_achievement(a)
trigger_unlock_event(a, event)
+ save
end
# Create a new event that marks the achievement unlocking.
redef class GameEntity
+ # Register a new game event for this entity.
fun add_event(event: GameEvent) do
event.owner = self
event.save
#
# To add events see `add_event`.
fun load_events: Array[GameEvent] do
- var key = key / "events"
+ var req = new JsonObject
+ req["game"] = game.key
+ req["owner"] = key
var res = new Array[GameEvent]
- if not game.store.has_collection(key) then return res
- var coll = game.store.list_collection(key)
- for id in coll do
- var name = id.to_s
- res.add load_event(name).as(not null)
+ for obj in game.db.collection("events").find_all(req) do
+ res.add new GameEvent.from_json(game, obj)
end
(new EventTimeComparator).sort(res)
return res
# Looks for the event save file in game data.
# Returns `null` if the event cannot be found.
fun load_event(id: String): nullable GameEvent do
- var key = key / "events" / id
- if not game.store.has_key(key) then return null
- var json = game.store.load_object(key)
- return new GameEvent.from_json(game, json)
+ var req = new JsonObject
+ req["game"] = game.key
+ req["owner"] = key
+ req["internal_id"] = id
+ var res = game.db.collection("events").find(req)
+ if res != null then return new GameEvent.from_json(game, res)
+ return null
end
end
class GameEvent
super GameEntity
+ redef var collection_name = "events"
redef var game
#
# Used to load events from json storage.
init from_json(game: Game, json: JsonObject) do
- init(game, json["kind"].to_s, json["data"].as(JsonObject))
- internal_id = json["internal_id"].to_s
- time = new ISODate.from_string(json["time"].to_s)
+ init(game, json["kind"].as(String), json["data"].as(JsonObject))
+ internal_id = json["internal_id"].as(String)
+ time = new ISODate.from_string(json["time"].as(String))
end
redef fun to_json do
json["time"] = time.to_s
json["data"] = data
json["game"] = game.key
+ var owner = self.owner
if owner != null then json["owner"] = owner.key
return json
end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Generate Github events from repo data.
+#
+# Mainly used for testing and history importation.
+module events_generator
+
+import github::events
+
+# Github events generator
+#
+# Generates events from repo data.
+class EventsGenerator
+
+ # API client used to get github data.
+ var api: GithubAPI
+
+ # Issues
+
+ # Generate a new IssuesEvent from an issue.
+ fun issues_event(action: String, issue: Issue): IssuesEvent do
+ var e = new IssuesEvent(api)
+ e.action = action
+ e.repo = issue.repo
+ e.issue = issue
+ return e
+ end
+
+ # Generate a new IssuesEvent with an `opened` action.
+ fun issue_open(issue: Issue): IssuesEvent do return issues_event("opened", issue)
+
+ # Generate a new IssuesEvent with an `closed` action.
+ fun issue_close(issue: Issue): IssuesEvent do return issues_event("closed", issue)
+
+ # Generate a new IssuesEvent with an `reopened` action.
+ fun issue_reopen(issue: Issue): IssuesEvent do return issues_event("reopened", issue)
+
+ # Generate a new IssuesEvent from a IssueEvent.
+ fun issue_raw_event(issue: Issue, event: IssueEvent): IssuesEvent do
+ var e = issues_event(event.event, issue)
+ e.lbl = event.labl
+ e.assignee = event.assignee
+ return e
+ end
+
+ # Pull requests
+
+ # Generate a new PullRequestEvent from a `pull` request.
+ fun pull_event(action: String, pull: PullRequest): PullRequestEvent do
+ var e = new PullRequestEvent(api)
+ e.action = action
+ e.repo = pull.repo
+ e.pull = pull
+ return e
+ end
+
+ # Generate a new PullRequestEvent with an `opened` action.
+ fun pull_open(pull: PullRequest): PullRequestEvent do return pull_event("opened", pull)
+
+ # Generate a new PullRequestEvent with an `closed` action.
+ fun pull_close(pull: PullRequest): PullRequestEvent do return pull_event("closed", pull)
+
+ # Generate a new PullRequestEvent with an `reopened` action.
+ fun pull_reopen(pull: PullRequest): PullRequestEvent do return pull_event("reopened", pull)
+
+ # Generate a new PullRequestEvent from a IssueEvent.
+ fun pull_raw_event(pull: PullRequest, event: IssueEvent): PullRequestEvent do
+ return pull_event(event.event, pull)
+ end
+
+ # Generate a new IssueCommentEvent from a IssueComment.
+ fun issue_comment_event(issue: Issue, comment: IssueComment): IssueCommentEvent do
+ var e = new IssueCommentEvent(api)
+ e.action = "created"
+ e.repo = issue.repo
+ e.issue = issue
+ e.comment = comment
+ return e
+ end
+end
# the `GameReactor` abstraction.
module game
-intrude import json::store
+import mongodb
import github::events
# An entity within a `Game`.
# The game instance containing `self`.
fun game: Game is abstract
- # Uniq key used for data storage.
- fun key: String is abstract
+ # Collection `self` should be saved in.
+ fun collection_name: String is abstract
- # Saves the `self` state in game data.
- #
- # Date are stored under `self.key`.
- fun save do game.store.store_object(key, to_json)
+ # Uniq key of this entity within the collection.
+ fun key: String is abstract
- # Saves `self` state under `key` data.
- #
- # Data are stored under `key / self.key`.
- fun save_in(key: String) do
- game.store.store_object(key / self.key, to_json)
- end
+ # Saves `self` in db.
+ fun save do game.db.collection(collection_name).save(to_json)
# Json representation of `self`.
- fun to_json: JsonObject do return new JsonObject
+ fun to_json: JsonObject do
+ var json = new JsonObject
+ json["_id"] = key
+ return json
+ end
# Pretty print `self` to be displayed in a terminal.
fun pretty: String is abstract
# Game name
var name: String = repo.full_name is lazy
- # Directory where game data are stored.
- var game_dir: String is lazy do return "nitrpg_data" / repo.full_name
-
redef var key = name is lazy
- # Used for data storage.
- #
- # File are stored in `game_dir`.
- var store: JsonStore is lazy do return new JsonStore(game_dir)
+ # Mongo server url where this game data are stored.
+ var mongo_url = "mongodb://localhost:27017" is writable
+
+ # Mongo db client.
+ var client = new MongoClient(mongo_url) is lazy
+
+ # Mongo db name where this game data are stored.
+ var db_name = "nitrpg" is writable
+
+ # Mongo db instance for this game.
+ var db: MongoDb is lazy do return client.database(db_name)
+
+ redef var collection_name = "games"
# Init the Game and try to load saved data.
- init do if store.has_key(key) then from_json(store.load_object(key))
+ init from_mongo(api: GithubAPI, repo: Repo) do
+ init(api, repo)
+ var req = new JsonObject
+ req["name"] = repo.full_name
+ var res = db.collection("games").find(req)
+ if res != null then from_json(res)
+ end
# Init `self` from a JsonObject.
#
# Returns `null` if the player cannot be found.
# In this case, the player can be created with `add_player`.
fun load_player(name: String): nullable Player do
- var key = "players" / name
- if not store.has_key(key) then return null
- var json = store.load_object(key)
- return new Player.from_json(self, json)
+ var req = new JsonObject
+ req["name"] = name
+ req["game"] = game.key
+ var res = db.collection("players").find(req)
+ if res != null then return new Player.from_json(self, res)
+ return null
end
# List known players.
#
# To add players see `add_player`.
fun load_players: MapRead[String, Player] do
+ var req = new JsonObject
+ req["game"] = game.key
var res = new HashMap[String, Player]
- if not store.has_collection("players") then return res
- var coll = store.list_collection("players")
- for id in coll do
- var name = id.to_s
- res[name] = load_player(name).as(not null)
+ for obj in db.collection("players").find_all(req) do
+ var player = new Player.from_json(self, obj)
+ res[player.name] = player
end
return res
end
end
# Erase all saved data for this game.
- fun clear do store.clear
+ fun clear do db.collection(collection_name).remove(to_json)
# Verbosity level used fo stdout.
#
class Player
super GameEntity
+ # Stored in collection `players`.
+ redef var collection_name = "players"
+
redef var game
# FIXME contructor should be private
#
# Used to load players from saved data.
init from_json(game: Game, json: JsonObject) do
- init(game, json["name"].to_s)
+ init(game, json["name"].as(String))
nitcoins = json["nitcoins"].as(Int)
end
redef var stats is lazy do return new GameStatsManager(game, self)
- redef fun save do
- super
- stats.save_in(self.key)
- end
-
redef fun pretty do
var res = new FlatBuffer
res.append super
res.append stats.pretty
return res.write_to_string
end
+
+ redef fun save do
+ super
+ stats.save
+ end
end
redef class Player
redef var stats is lazy do return new GameStatsManager(game, self)
- redef fun save do
- super
- stats.save_in(self.key)
- end
-
redef fun nitcoins do return stats["nitcoins"]
redef fun nitcoins=(nc) do stats["nitcoins"] = nc
res.append stats.pretty
return res.write_to_string
end
+
+ redef fun save do
+ super
+ stats.save
+ end
end
# Store game stats for defined period.
# Load statistics for a `period` key.
fun load_stats_for(period: String): GameStats do
- var key = owner.key / self.key / period
- if not game.store.has_key(key) then
+ var req = new JsonObject
+ req["period"] = period
+ req["owner"] = owner.key
+ var obj = game.db.collection("statistics").find(req)
+ if obj isa JsonObject then
+ return new GameStats.from_json(game, period, owner, obj)
+ else
return new GameStats(game, period, owner)
end
- var json = game.store.load_object(key)
- return new GameStats.from_json(game, period, owner, json)
end
redef fun [](key) do return overall[key]
weekly.dec(e)
end
- redef fun save_in(key) do
- overall.save_in(key / self.key)
- yearly.save_in(key / self.key)
- monthly.save_in(key / self.key)
- daily.save_in(key / self.key)
- weekly.save_in(key / self.key)
+ redef fun save do
+ overall.save
+ yearly.save
+ monthly.save
+ daily.save
+ weekly.save
end
redef fun pretty do return overall.pretty
redef var game
+ redef var collection_name = "statistics"
+
# The period these stats are about.
var period: String
redef class GameEntity
+ # Path to this entity from root.
+ fun path: String do return collection_name / key
+
# URL to this game entity page.
- fun url: String do return game.url / key
+ fun url: String do return game.url / path
end
redef class Game
# This must be set before any access to `url`.
var root_url: String is noinit, writable
- redef fun url do return "{root_url}/games" / key
+ redef fun url do return "{root_url}/{path}"
# Return a HTML link to this Game.
fun link: String do return "<a href=\"{url}\">{name}</a>"
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test module for `achievements.nit`
+module test_achievements is test_suite
+
+import test_helper
+import achievements
+
+class TestGame
+ super NitrpgTestHelper
+
+ fun test_add_achievement do
+ var db = load_db("test_add_achievement")
+ var game = load_game("Morriar/nit", db)
+ var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
+ var a2 = new Achievement(game, "test_id2", "test_name", "test_desc", 15)
+ game.add_achievement(a1)
+ game.add_achievement(a2)
+ assert game.load_achievements.length == 2
+ db.drop
+ end
+
+ fun test_load_achievement do
+ var db = load_db("test_load_achievement")
+ var game = load_game("Morriar/nit", db)
+ var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
+ var a2 = new Achievement(game, "test_id2", "test_name", "test_desc", 15)
+ game.add_achievement(a1)
+ assert game.load_achievement(a1.id).id == "test_id1"
+ assert game.load_achievement(a2.id) == null
+ db.drop
+ end
+
+ fun test_load_achievements do
+ var db = load_db("test_load_achievements")
+ var game = load_game("Morriar/nit", db)
+ var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
+ var a2 = new Achievement(game, "test_id2", "test_name", "test_desc", 15)
+ var a3 = new Achievement(game, "test_id3", "test_name", "test_desc", 15)
+ game.add_achievement(a1)
+ game.add_achievement(a2)
+ game.db.collection("achievements").insert(a3.to_json)
+ var ok = [a1.id, a2.id]
+ var res = game.load_achievements
+ assert res.length == 2
+ for a in res.values do assert ok.has(a.id)
+ db.drop
+ end
+end
+
+class TestPlayer
+ super NitrpgTestHelper
+
+ fun test_add_achievement do
+ var db = load_db("test_add_achievement")
+ var game = load_game("Morriar/nit", db)
+ var player1 = new Player(game, "Morriar")
+ var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
+ var a2 = new Achievement(game, "test_id2", "test_name", "test_desc", 15)
+ player1.add_achievement(a1)
+ player1.add_achievement(a2)
+ assert player1.load_achievements.length == 2
+ db.drop
+ end
+
+ fun test_load_achievement do
+ var db = load_db("test_load_achievement")
+ var game = load_game("Morriar/nit", db)
+ var player1 = new Player(game, "Morriar")
+ var player2 = new Player(game, "xymus")
+ var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
+ var a2 = new Achievement(game, "test_id2", "test_name", "test_desc", 15)
+ player1.add_achievement(a1)
+ player2.add_achievement(a2)
+ assert player1.load_achievement(a1.id).id == "test_id1"
+ assert player1.load_achievement(a2.id) == null
+ assert player2.load_achievement(a2.id).id == "test_id2"
+ assert player2.load_achievement(a1.id) == null
+ db.drop
+ end
+
+ fun test_load_achievements do
+ var db = load_db("test_load_achievements")
+ var game = load_game("Morriar/nit", db)
+ var player1 = new Player(game, "Morriar")
+ var player2 = new Player(game, "xymus")
+ var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
+ var a2 = new Achievement(game, "test_id2", "test_name", "test_desc", 15)
+ var a3 = new Achievement(game, "test_id3", "test_name", "test_desc", 15)
+ player1.add_achievement(a1)
+ player1.add_achievement(a2)
+ player2.add_achievement(a3)
+ var ok = [a1.id, a2.id]
+ var res = player1.load_achievements
+ assert res.length == 2
+ for a in res.values do assert ok.has(a.id)
+ db.drop
+ end
+end
+
+class TestAchievement
+ super NitrpgTestHelper
+
+ fun test_init do
+ var db = load_db("test_init")
+ var game = load_game("Morriar/nit", db)
+ var a = new Achievement(game, "test_id", "test_name", "test_desc", 15)
+ assert a.id == "test_id"
+ assert a.name == "test_name"
+ assert a.desc == "test_desc"
+ assert a.reward == 15
+ db.drop
+ end
+
+ fun test_init_from_json do
+ var db = load_db("test_init_from_json")
+ var game = load_game("Morriar/nit", db)
+ var json = """{
+ "id": "test_id",
+ "name": "test_name",
+ "desc": "test_desc",
+ "reward": 15
+ }""".parse_json.as(JsonObject)
+ var a = new Achievement.from_json(game, json)
+ assert a.id == "test_id"
+ assert a.name == "test_name"
+ assert a.desc == "test_desc"
+ assert a.reward == 15
+ db.drop
+ end
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test module for `events.nit`
+module test_events is test_suite
+
+import test_helper
+import events
+
+class TestGame
+ super NitrpgTestHelper
+
+ fun test_add_event do
+ var db = load_db("test_add_event")
+ var game = load_game("Morriar/nit", db)
+ var event1 = new GameEvent(game, "test_kind", new JsonObject)
+ var event2 = new GameEvent(game, "test_kind", new JsonObject)
+ game.add_event(event1)
+ game.add_event(event2)
+ assert game.load_events.length == 2
+ db.drop
+ end
+
+ fun test_load_event do
+ var db = load_db("test_load_event")
+ var game = load_game("Morriar/nit", db)
+ var event1 = new GameEvent(game, "test_kind", new JsonObject)
+ var event2 = new GameEvent(game, "test_kind", new JsonObject)
+ game.add_event(event1)
+ assert game.load_event(event1.internal_id).kind == "test_kind"
+ assert game.load_event(event2.internal_id) == null
+ db.drop
+ end
+
+ fun test_load_events do
+ var db = load_db("test_load_events")
+ var game = load_game("Morriar/nit", db)
+ var event1 = new GameEvent(game, "test_kind", new JsonObject)
+ var event2 = new GameEvent(game, "test_kind", new JsonObject)
+ var event3 = new GameEvent(game, "test_kind", new JsonObject)
+ game.add_event(event1)
+ game.add_event(event2)
+ game.db.collection("events").insert(event3.to_json)
+ var ok = [event1.internal_id, event2.internal_id]
+ var res = game.load_events
+ assert res.length == 2
+ for event in res do assert ok.has(event.internal_id)
+ db.drop
+ end
+end
+
+class TestPlayer
+ super NitrpgTestHelper
+
+ fun test_add_event do
+ var db = load_db("test_add_event")
+ var game = load_game("Morriar/nit", db)
+ var player1 = new Player(game, "Morriar")
+ var player2 = new Player(game, "xymus")
+ var event1 = new GameEvent(game, "test_kind", new JsonObject)
+ var event2 = new GameEvent(game, "test_kind", new JsonObject)
+ player1.add_event(event1)
+ player1.add_event(event2)
+ assert player1.load_events.length == 2
+ assert player2.load_events.length == 0
+ db.drop
+ end
+
+ fun test_load_event do
+ var db = load_db("test_load_event")
+ var game = load_game("Morriar/nit", db)
+ var player1 = new Player(game, "Morriar")
+ var player2 = new Player(game, "xymus")
+ var event1 = new GameEvent(game, "test_kind", new JsonObject)
+ var event2 = new GameEvent(game, "test_kind", new JsonObject)
+ player1.add_event(event1)
+ player2.add_event(event2)
+ assert player1.load_event(event1.internal_id).kind == "test_kind"
+ assert player1.load_event(event2.internal_id) == null
+ assert player2.load_event(event2.internal_id).kind == "test_kind"
+ assert player2.load_event(event1.internal_id) == null
+ db.drop
+ end
+
+ fun test_load_events do
+ var db = load_db("test_load_events")
+ var game = load_game("Morriar/nit", db)
+ var player1 = new Player(game, "Morriar")
+ var player2 = new Player(game, "xymus")
+ var event1 = new GameEvent(game, "test_kind", new JsonObject)
+ var event2 = new GameEvent(game, "test_kind", new JsonObject)
+ var event3 = new GameEvent(game, "test_kind", new JsonObject)
+ player1.add_event(event1)
+ player1.add_event(event2)
+ player2.add_event(event3)
+ assert player1.load_events.length == 2
+ assert player2.load_events.length == 1
+ var ok = [event1.internal_id, event2.internal_id]
+ for event in player1.load_events do assert ok.has(event.internal_id)
+ db.drop
+ end
+end
+
+class TestGameEvent
+ super NitrpgTestHelper
+
+ fun test_init do
+ var db = load_db("test_init")
+ var game = load_game("Morriar/nit", db)
+ var event = new GameEvent(game, "test_kind", new JsonObject)
+ assert event.to_json["kind"] == "test_kind"
+ db.drop
+ end
+
+ fun test_init_from_json do
+ var db = load_db("test_init_from_json")
+ var game = load_game("Morriar/nit", db)
+ var json = """{
+ "internal_id": "test_id",
+ "kind": "test_kind",
+ "time": "2015-02-05T00:00:00Z",
+ "data": {"test_field": "test_value"}
+ }""".parse_json.as(JsonObject)
+ var event = new GameEvent.from_json(game, json)
+ assert event.internal_id == "test_id"
+ assert event.kind == "test_kind"
+ assert event.data.to_json == """{"test_field":"test_value"}"""
+ assert event.time.to_s == "2015-02-05T00:00:00Z"
+ db.drop
+ end
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test module for `game.nit`.
+module test_game is test_suite
+
+import test_helper
+
+class TestGame
+ super NitrpgTestHelper
+
+ fun test_add_player do
+ var db = load_db("test_add_player")
+ var game = load_game("privat/nit", db)
+ var users = ["Morriar", "xymus"]
+ for name in users do
+ game.add_player(game.api.load_user(name).as(not null))
+ end
+ var res = game.load_players.values
+ assert res.length == 2
+ for player in res do
+ assert users.has(player.name)
+ end
+ db.drop
+ end
+
+ fun test_load_player do
+ var db = load_db("test_load_player")
+ var game = load_game("privat/nit", db)
+ var ogame = load_game("Morriar/nit", db)
+
+ var player1 = new Player(game, "Morriar")
+ var player2 = new Player(ogame, "privat")
+ game.db.collection("players").insert(player1.to_json)
+ ogame.db.collection("players").insert(player2.to_json)
+
+ assert game.load_player("privat") == null
+ assert game.load_player("Morriar").name == "Morriar"
+ assert ogame.load_player("privat").name == "privat"
+ assert ogame.load_player("Morriar") == null
+ db.drop
+ end
+
+ fun test_load_players do
+ var db = load_db("test_load_players")
+ var game = load_game("privat/nit", db)
+ var ogame = load_game("Morriar/nit", db)
+
+ var player1 = new Player(game, "Morriar")
+ var player2 = new Player(ogame, "privat")
+ var player3 = new Player(game, "xymus")
+ game.db.collection("players").insert(player1.to_json)
+ ogame.db.collection("players").insert(player2.to_json)
+ game.db.collection("players").insert(player3.to_json)
+
+ var players = game.load_players
+ var ok = ["Morriar", "xymus"]
+ for player in players.values do assert ok.has(player.name)
+ db.drop
+ end
+end
+
+class TestPlayer
+ super NitrpgTestHelper
+
+ fun test_init do
+ var db = load_db("test_init")
+ var game = load_game("privat/nit", db)
+ var player = new Player(game, "Morriar")
+ assert player.name == "Morriar"
+ assert player.user.login == "Morriar"
+ assert player.nitcoins == 0
+ db.drop
+ end
+
+ fun test_init_from_json do
+ var db = load_db("test_init_from_json")
+ var game = load_game("privat/nit", db)
+ var json = """{"name": "Morriar", "nitcoins": 10}""".parse_json
+ var player = new Player.from_json(game, json.as(JsonObject))
+ assert player.name == "Morriar"
+ assert player.user.login == "Morriar"
+ assert player.nitcoins == 10
+ db.drop
+ end
+
+ fun test_save do
+ var db = load_db("test_save")
+ var game = load_game("privat/nit", db)
+ var json = """{"name": "Morriar", "nitcoins": 10}""".parse_json.as(JsonObject)
+ var player = new Player.from_json(game, json)
+ player.save
+ assert game.db.collection("players").find(json) != null
+ db.drop
+ end
+
+ fun test_game_add_player do
+ var db = load_db("test_game_add_player")
+ var game = load_game("privat/nit", db)
+ game.add_player(game.api.load_user("Morriar").as(not null))
+ var json = """{"name": "Morriar"}""".parse_json.as(JsonObject)
+ assert game.db.collection("players").find(json) != null
+ db.drop
+ end
+
+ fun test_game_load_player do
+ var db = load_db("test_game_load_player")
+ var game = load_game("privat/nit", db)
+ var json = """{"name": "Morriar", "nitcoins": 10}""".parse_json.as(JsonObject)
+ var player = new Player.from_json(game, json)
+ player.save
+ var oplayer = game.load_player("Morriar")
+ assert oplayer != null
+ assert player.nitcoins == oplayer.nitcoins
+ db.drop
+ end
+end
+
+class TestUser
+ super NitrpgTestHelper
+
+ fun test_player do
+ var db = load_db("test_player")
+ var api = new GithubAPI(get_github_oauth)
+ var game = load_game("privat/nit", db)
+ var user = api.load_user("Morriar")
+ assert user != null
+ var player = user.player(game)
+ assert player.name == "Morriar"
+ game.clear
+ db.drop
+ end
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test tools for NitRPG.
+module test_helper is test_suite
+
+import test_suite
+import game
+import github::cache
+
+# Used to factorize test treatments.
+abstract class NitrpgTestHelper
+ super TestSuite
+
+ # Github API client
+ var api: GithubAPI do
+ var api = new GithubAPI(get_github_oauth)
+ api.enable_cache = true
+ return api
+ end
+
+ # Mongo API client
+ var mongo = new MongoClient("mongodb://localhost:27017/")
+
+ # Load a new test database by with a name
+ fun load_db(name: String): MongoDb do return mongo.database(name)
+
+ # Load a repo by its name.
+ fun load_repo(name: String): Repo do
+ var repo = api.load_repo(name)
+ assert repo != null
+ return repo
+ end
+
+ # Load a game by its name.
+ fun load_game(name: String, db: MongoDb): Game do
+ var game = new Game(api, load_repo(name))
+ game.db_name = db.name
+ return game
+ end
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test module for `listener.nit`
+module test_listener is test_suite
+
+import test_helper
+import reactors
+import achievements
+import events_generator
+
+private class DummyListener
+ super NitrpgTestHelper
+
+ var reactors = new Array[GameReactor]
+
+ fun apply_event(event: GithubEvent, db: MongoDb) do
+ var game = load_game(event.repo.full_name, db)
+ for reactor in reactors do
+ reactor.react_event(game, event)
+ end
+ end
+
+ fun add_reactor(reactors: GameReactor...) do self.reactors.add_all reactors
+end
+
+class TestListener
+ super NitrpgTestHelper
+
+ var generator = new EventsGenerator(api)
+
+ var repo: Repo is lazy do return load_repo("Morriar/nit")
+
+ fun test_game_issue_stats do
+ var db = load_db("test_game_issue_stats")
+ var l = new DummyListener
+ l.add_reactor(new StatisticsReactor)
+
+ var issue = api.load_issue(repo, 322)
+ assert issue != null
+
+ l.apply_event(generator.issue_open(issue), db)
+ var game = load_game("Morriar/nit", db)
+ assert game.stats.overall["issues"] == 1
+ assert game.stats.overall["issues_open"] == 1
+ l.apply_event(generator.issue_close(issue), db)
+ game = load_game("Morriar/nit", db)
+ assert game.stats.overall["issues"] == 1
+ assert game.stats.overall["issues_open"] == 0
+ l.apply_event(generator.issue_reopen(issue), db)
+ game = load_game("Morriar/nit", db)
+ assert game.stats.overall["issues"] == 1
+ assert game.stats.overall["issues_open"] == 1
+ db.drop
+ end
+
+ fun test_player_issue_stats do
+ var db = load_db("test_player_issue_stats")
+ var game = load_game("Morriar/nit", db)
+ var l = new DummyListener
+ l.add_reactor(new StatisticsReactor)
+
+ var issue = api.load_issue(repo, 322)
+ assert issue != null
+
+ l.apply_event(generator.issue_open(issue), db)
+ var player = new Player(game, "Morriar")
+ assert player.stats.overall["issues"] == 1
+ assert player.stats.overall["issues_open"] == 1
+ l.apply_event(generator.issue_close(issue), db)
+ player = new Player(game, "Morriar")
+ assert player.stats.overall["issues"] == 1
+ assert player.stats.overall["issues_open"] == 0
+ l.apply_event(generator.issue_reopen(issue), db)
+ player = new Player(game, "Morriar")
+ assert player.stats.overall["issues"] == 1
+ assert player.stats.overall["issues_open"] == 1
+ db.drop
+ end
+
+ fun test_game_pr_stats do
+ var db = load_db("test_game_pr_stats")
+ var l = new DummyListener
+ l.add_reactor(new StatisticsReactor)
+
+ var pr = api.load_pull(repo, 275)
+ assert pr != null
+
+ l.apply_event(generator.pull_open(pr), db)
+ var game = load_game("Morriar/nit", db)
+ assert game.stats.overall["pulls"] == 1
+ assert game.stats.overall["pulls_open"] == 1
+ assert game.stats.overall["commits"] == 0
+ pr.merged = false
+ l.apply_event(generator.pull_close(pr), db)
+ game = load_game("Morriar/nit", db)
+ assert game.stats.overall["pulls"] == 1
+ assert game.stats.overall["pulls_open"] == 0
+ assert game.stats.overall["commits"] == 0
+ l.apply_event(generator.pull_reopen(pr), db)
+ game = load_game("Morriar/nit", db)
+ assert game.stats.overall["pulls"] == 1
+ assert game.stats.overall["pulls_open"] == 1
+ assert game.stats.overall["commits"] == 0
+ pr.merged = true
+ l.apply_event(generator.pull_close(pr), db)
+ game = load_game("Morriar/nit", db)
+ assert game.stats.overall["pulls"] == 1
+ assert game.stats.overall["pulls_open"] == 0
+ assert game.stats.overall["commits"] == 2
+ db.drop
+ end
+
+ fun test_game_issue_comment_stats do
+ var db = load_db("test_game_issue_comment_stats")
+ var l = new DummyListener
+ l.add_reactor(new StatisticsReactor)
+
+ var issue = api.load_issue(repo, 322)
+ assert issue != null
+ var comment = api.load_issue_comment(repo, 76119442)
+ assert comment != null
+
+ comment.body = "foo bar"
+ l.apply_event(generator.issue_comment_event(issue, comment), db)
+ var game = load_game("Morriar/nit", db)
+ assert game.stats.overall["comments"] == 1
+ assert game.stats.overall["reviews"] == 0
+ comment.body = "foo +1 bar"
+ l.apply_event(generator.issue_comment_event(issue, comment), db)
+ game = load_game("Morriar/nit", db)
+ assert game.stats.overall["comments"] == 2
+ assert game.stats.overall["reviews"] == 1
+ db.drop
+ end
+
+ fun test_player_pull_reactor do
+ var db = load_db("test_player_pull_reactor")
+ var game = load_game("Morriar/nit", db)
+ var l = new DummyListener
+ l.add_reactor(new PlayerReactor)
+
+ var pull = api.load_pull(repo, 275)
+ assert pull != null
+
+ l.apply_event(generator.pull_open(pull), db)
+ var player = new Player(game, "itch76")
+ assert player.stats.overall["nitcoins"] == 10
+ pull.merged = false
+ l.apply_event(generator.pull_close(pull), db)
+ player = new Player(game, "itch76")
+ assert player.stats.overall["nitcoins"] == 0
+ l.apply_event(generator.pull_reopen(pull), db)
+ player = new Player(game, "itch76")
+ assert player.stats.overall["nitcoins"] == 10
+ pull.merged = true
+ l.apply_event(generator.pull_close(pull), db)
+ player = new Player(game, "itch76")
+ assert player.stats.overall["nitcoins"] == 12
+ db.drop
+ end
+
+ fun test_player_review_reactor do
+ var db = load_db("test_player_review_reactor")
+ var game = load_game("Morriar/nit", db)
+ var l = new DummyListener
+ l.add_reactor(new PlayerReactor)
+
+ var pull = api.load_pull(repo, 275)
+ assert pull != null
+ var comment = api.load_issue_comment(repo, 36961230)
+ assert comment != null
+
+ # TODO handle multiple review by the same user
+
+ # no review in opened issue
+ pull.state = "open"
+ comment.body = "foo bar"
+ l.apply_event(generator.issue_comment_event(pull, comment), db)
+ var player = new Player(game, "Morriar")
+ assert player.stats.overall["nitcoins"] == 0
+
+ # review in opened issue
+ pull.state = "open"
+ comment.body = "foo +1 bar"
+ l.apply_event(generator.issue_comment_event(pull, comment), db)
+ player = new Player(game, "Morriar")
+ print player.stats.overall["nitcoins"]
+ assert player.stats.overall["nitcoins"] == 2
+
+ # review in closed issue
+ pull.state = "closed"
+ comment.body = "foo +1 bar"
+ l.apply_event(generator.issue_comment_event(pull, comment), db)
+ player = new Player(game, "Morriar")
+ assert player.stats.overall["nitcoins"] == 2
+
+ # review in reopened issue
+ pull.state = "open"
+ comment.body = "foo +1 bar"
+ l.apply_event(generator.issue_comment_event(pull, comment), db)
+ player = new Player(game, "Morriar")
+ assert player.stats.overall["nitcoins"] == 4
+ db.drop
+ end
+
+ fun test_X_issues_achievements do
+ var db = load_db("test_X_issues_achievements")
+ var game = load_game("Morriar/nit", db)
+ var l = new DummyListener
+ l.add_reactor(new StatisticsReactor)
+ l.add_reactor(new Player1Issue, new Player100Issues, new Player1KIssues)
+
+ var issue = api.load_issue(repo, 322)
+ assert issue != null
+
+ for i in [0, 99, 999] do
+ var id = "player_{i + 1}_issue"
+ if i > 0 then id = "{id}s"
+ var player = new Player(game, "Morriar")
+ player.stats["issues"] = i
+ player.save
+ l.apply_event(generator.issue_open(issue), db)
+ assert player.load_achievements.has_key(id)
+ end
+ var player = new Player(game, "Morriar")
+ assert player.stats.overall["nitcoins"] == 1110
+ db.drop
+ end
+
+ fun test_X_pulls_achievements do
+ var db = load_db("test_X_pulls_achievements")
+ var game = load_game("Morriar/nit", db)
+ var l = new DummyListener
+ l.add_reactor(new StatisticsReactor)
+ l.add_reactor(new Player1Pull, new Player100Pulls, new Player1KPulls)
+
+ var pull = api.load_pull(repo, 275)
+ assert pull != null
+
+ for i in [0, 99, 999] do
+ var id = "player_{i + 1}_pull"
+ if i > 0 then id = "{id}s"
+ var player = new Player(game, "itch76")
+ player.stats["pulls"] = i
+ player.save
+ l.apply_event(generator.pull_open(pull), db)
+ assert player.load_achievements.has_key(id)
+ end
+ var player = new Player(game, "itch76")
+ assert player.stats.overall["nitcoins"] == 1110
+ db.drop
+ end
+
+ fun test_X_commits_achievements do
+ var db = load_db("test_X_commits_achievements")
+ var game = load_game("Morriar/nit", db)
+ var l = new DummyListener
+ l.add_reactor(new StatisticsReactor)
+ l.add_reactor(new Player1Commit, new Player100Commits)
+ l.add_reactor(new Player1KCommits, new Player10KCommits)
+
+ var pull = api.load_pull(repo, 275)
+ assert pull != null
+ pull.state = "closed"
+ pull.merged = true
+
+ for i in [0, 99, 999, 9999] do
+ var id = "player_{i + 1}_commit"
+ if i > 0 then id = "{id}s"
+ var player = new Player(game, "itch76")
+ player.stats["commits"] = i
+ player.save
+ l.apply_event(generator.pull_close(pull), db)
+ assert player.load_achievements.has_key(id)
+ end
+ var player = new Player(game, "itch76")
+ assert player.stats.overall["nitcoins"] == 11110
+ db.drop
+ end
+
+ fun test_X_comments_achievements do
+ var db = load_db("test_X_comments_achievements")
+ var game = load_game("Morriar/nit", db)
+ var l = new DummyListener
+ l.add_reactor(new StatisticsReactor)
+ l.add_reactor(new Player1Comment, new Player100Comments, new Player1KComments)
+
+ var pull = api.load_pull(repo, 275)
+ assert pull != null
+ var comment = api.load_issue_comment(repo, 36961230)
+ assert comment != null
+
+ for i in [0, 99, 999] do
+ var id = "player_{i + 1}_comment"
+ if i > 0 then id = "{id}s"
+ var player = new Player(game, "Morriar")
+ player.stats["comments"] = i
+ player.save
+ l.apply_event(generator.issue_comment_event(pull, comment), db)
+ assert player.load_achievements.has_key(id)
+ end
+ var player = new Player(game, "Morriar")
+ assert player.stats.overall["nitcoins"] == 1110
+ db.drop
+ end
+
+ fun test_issues_achievements do
+ var db = load_db("test_issues_achievements")
+ var game = load_game("Morriar/nit", db)
+ var l = new DummyListener
+ l.add_reactor(new IssueAboutNitdoc, new IssueAboutFFI)
+
+ var issue = api.load_issue(repo, 322)
+ assert issue != null
+
+ issue.title = "nitdoc ffi"
+ l.apply_event(generator.issue_open(issue), db)
+ var player = new Player(game, "Morriar")
+ assert player.load_achievements.has_key("issue_about_nitdoc")
+ assert player.load_achievements.has_key("issue_about_ffi")
+ assert player.stats.overall["nitcoins"] == 20
+ db.drop
+ end
+
+ fun test_comments_reactor do
+ var db = load_db("test_comments_reactor")
+ var game = load_game("Morriar/nit", db)
+ var l = new DummyListener
+ l.add_reactor(new PlayerPingGod, new PlayerFirstReview, new PlayerSaysNitcoin)
+
+ var pull = api.load_pull(repo, 275)
+ assert pull != null
+ var comment = api.load_issue_comment(repo, 36961230)
+ assert comment != null
+
+ comment.body = "@{game.repo.owner.login}"
+ l.apply_event(generator.issue_comment_event(pull, comment), db)
+ var player = new Player(game, "Morriar")
+ assert player.load_achievements.has_key("player_ping_god")
+ assert player.stats.overall["nitcoins"] == 50
+
+ comment.body = "+1"
+ l.apply_event(generator.issue_comment_event(pull, comment), db)
+ player = new Player(game, "Morriar")
+ assert player.load_achievements.has_key("player_first_review")
+ assert player.stats.overall["nitcoins"] == 60
+
+ comment.body = "Nitcoins"
+ l.apply_event(generator.issue_comment_event(pull, comment), db)
+ player = new Player(game, "Morriar")
+ assert player.load_achievements.has_key("player_says_nitcoin")
+ assert player.stats.overall["nitcoins"] == 70
+ db.drop
+ end
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test module for `stats.nit`
+module test_statistics is test_suite
+
+import test_helper
+import statistics
+
+class TestGame
+ super NitrpgTestHelper
+
+ fun test_game_stats do
+ var db = load_db("test_game_stats")
+ var game = load_game("privat/nit", db)
+ var stats = game.stats
+ assert stats.overall["test"] == 0
+ stats.overall.inc("test")
+ assert stats.overall["test"] == 1
+ stats.save
+ var ogame = load_game("privat/nit", db)
+ var ostats = ogame.stats
+ ostats.overall.inc("test")
+ assert ostats.overall["test"] == 2
+ db.drop
+ end
+end
+
+class TestPlayer
+ super NitrpgTestHelper
+
+ fun test_player_stats do
+ var db = load_db("test_player_stats")
+ var game = load_game("privat/nit", db)
+ var player = new Player(game, "Morriar")
+ var stats = player.stats
+ assert stats.overall["test"] == 0
+ stats.overall.inc("test")
+ assert stats.overall["test"] == 1
+ stats.save
+ var oplayer = new Player(game, "Morriar")
+ var ostats = oplayer.stats
+ ostats.overall.inc("test")
+ assert ostats.overall["test"] == 2
+ db.drop
+ end
+end
+
+class TestGameStats
+ super NitrpgTestHelper
+
+ fun test_init_from_json do
+ var db = load_db("test_init_from_json")
+ var game = load_game("privat/nit", db)
+ var owner = new Player(game, "Morriar")
+ var json = """{
+ "period": "2015",
+ "owner": "Morriar",
+ "values": {
+ "test1": 10,
+ "test2": 20
+ }
+ }""".parse_json.as(JsonObject)
+ var stats = new GameStats.from_json(game, "2015", owner, json)
+ assert stats["test0"] == 0
+ assert stats["test1"] == 10
+ assert stats["test2"] == 20
+ db.drop
+ end
+end
# Returns the game with `name` or null if no game exists with this name.
fun load_game(name: String): nullable Game do
var repo = api.load_repo(name)
- if api.was_error or repo == null then return null
- var game = new Game(api, repo)
+ if repo == null then return null
+ var game = new Game.from_mongo(api, repo)
game.root_url = root_url
return game
end
# Returns the list of saved games from NitRPG data.
fun load_games: Array[Game] do
var res = new Array[Game]
- var rpgdir = "nitrpg_data"
- if not rpgdir.file_exists then return res
- for user in rpgdir.files do
- for repo in "{rpgdir}/{user}".files do
- var game = load_game("{user}/{repo}")
- if game != null then res.add game
- end
+ # TODO should be option
+ var mongo = new MongoClient("mongodb://localhost:27017")
+ var db = mongo.database("nitrpg")
+ for obj in db.collection("games").find_all(new JsonObject) do
+ var repo = api.load_repo(obj["name"].to_s)
+ assert repo != null
+ var game = new Game(api, repo)
+ game.from_json(obj)
+ game.root_url = root_url
+ res.add game
end
return res
end
real_collection.push(e)
mutex.unlock
end
+
+ redef fun shift
+ do
+ mutex.lock
+ var value = real_collection.shift
+ mutex.unlock
+ return value
+ end
end
--- /dev/null
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Simple example of joinable task using threadpool
+module jointask_example
+
+import threadpool
+
+# Task computing a string
+class StringTask
+ super JoinTask
+
+ # Sleeping time
+ var sec: Int
+
+ # result of `self` execution
+ var value: String
+
+ # ID for printing
+ var id: Int
+
+ redef fun main do
+ nanosleep(sec, 0)
+ value += " id: {id}"
+ end
+end
+
+var tp = new ThreadPool
+var t0 = new StringTask(10, "First, long task", 0)
+var tasks = new Array[StringTask]
+for i in 5.times do
+ tasks.add(new StringTask(1, "Small task", i + 1))
+end
+tp.execute(t0)
+for t in tasks do tp.execute(t)
+for t in tasks do
+ t.join
+ print t.value
+end
+t0.join
+print t0.value
# Task printing "hello world" on standard output
class HWTask
- super Task
+ super JoinTask
# Sleeping time
var sec: Int
# A simple ThreadPool implemented with an array
class ThreadPool
- private var queue = new ConcurrentList[Task].wrap(new List[Task])
+ private var queue = new ConcurrentList[JoinTask].wrap(new List[JoinTask])
private var mutex = new Mutex
private var cond = new NativePthreadCond
private fun set_nb_threads(nb: nullable Int) is autoinit do nb_threads = nb or else 5
# Adds a Task into the queue
- fun execute(task: Task) do
+ fun execute(task: JoinTask) do
queue.push(task)
cond.signal
end
private class PoolThread
super Thread
- var queue: ConcurrentList[Task]
+ var queue: ConcurrentList[JoinTask]
var mutex: Mutex
var cond : NativePthreadCond
redef fun main do
loop
- var t: nullable Task = null
+ var t: nullable JoinTask = null
mutex.lock
if queue.is_empty then cond.wait(mutex.native.as(not null))
if not queue.is_empty then
- t = queue.pop
+ t = queue.shift
end
mutex.unlock
- if t != null then t.main
+ if t != null then
+ t.main
+ t.mutex.lock
+ t.is_done = true
+ var tcond = t.cond
+ if tcond != null then tcond.signal
+ t.mutex.unlock
+ end
+ end
+ end
+end
+
+# A Task which is joinable, meaning it can return a value and if the value is not set yet, it blocks the execution
+class JoinTask
+ super Task
+
+ # Is `self` done ?
+ var is_done = false
+
+ private var mutex = new Mutex
+ private var cond: nullable NativePthreadCond = null
+
+ # Return immediatly if the task terminated, or block waiting for `self` to terminate
+ fun join do
+ mutex.lock
+ if not is_done then
+ var cond = new NativePthreadCond
+ self.cond = cond
+ cond.wait(mutex.native.as(not null))
end
+ mutex.unlock
end
end
--- /dev/null
+Small task id: 1
+Small task id: 2
+Small task id: 3
+Small task id: 4
+Small task id: 5
+First, long task id: 0