From: Jean Privat Date: Sat, 5 Dec 2015 21:52:51 +0000 (-0500) Subject: Merge: Perfize nitcc_runtime X-Git-Tag: v0.8~52 X-Git-Url: http://nitlanguage.org?hp=74136ba96f20f38af484ed96a6bec54d5fc9cdf6 Merge: Perfize nitcc_runtime 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 --- diff --git a/contrib/nitrpg/Makefile b/contrib/nitrpg/Makefile index 40ebec2..b6c4be9 100644 --- a/contrib/nitrpg/Makefile +++ b/contrib/nitrpg/Makefile @@ -15,6 +15,7 @@ # limitations under the License. NITC=../../bin/nitc +NITU=../../bin/nitunit all: listener web @@ -24,5 +25,12 @@ listener: 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 diff --git a/contrib/nitrpg/src/achievements.nit b/contrib/nitrpg/src/achievements.nit index 5ce317a..24fac20 100644 --- a/contrib/nitrpg/src/achievements.nit +++ b/contrib/nitrpg/src/achievements.nit @@ -32,22 +32,28 @@ redef class GameEntity # # 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. @@ -56,12 +62,13 @@ redef class GameEntity # # 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 @@ -74,10 +81,11 @@ 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}" @@ -105,7 +113,11 @@ class Achievement # # 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 @@ -115,18 +127,13 @@ class Achievement 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 @@ -140,6 +147,7 @@ redef class Player nitcoins += a.reward add_achievement(a) trigger_unlock_event(a, event) + save end # Create a new event that marks the achievement unlocking. diff --git a/contrib/nitrpg/src/events.nit b/contrib/nitrpg/src/events.nit index bd1ee58..c540dc5 100644 --- a/contrib/nitrpg/src/events.nit +++ b/contrib/nitrpg/src/events.nit @@ -24,6 +24,7 @@ import game redef class GameEntity + # Register a new game event for this entity. fun add_event(event: GameEvent) do event.owner = self event.save @@ -35,13 +36,12 @@ redef class GameEntity # # 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 @@ -52,10 +52,13 @@ redef class GameEntity # 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 @@ -63,6 +66,7 @@ end class GameEvent super GameEntity + redef var collection_name = "events" redef var game @@ -95,9 +99,9 @@ class GameEvent # # 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 @@ -107,6 +111,7 @@ class GameEvent 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 diff --git a/contrib/nitrpg/src/events_generator.nit b/contrib/nitrpg/src/events_generator.nit new file mode 100644 index 0000000..cc8b6a4 --- /dev/null +++ b/contrib/nitrpg/src/events_generator.nit @@ -0,0 +1,94 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# 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 diff --git a/contrib/nitrpg/src/game.nit b/contrib/nitrpg/src/game.nit index 2924e46..504c445 100644 --- a/contrib/nitrpg/src/game.nit +++ b/contrib/nitrpg/src/game.nit @@ -25,7 +25,7 @@ # the `GameReactor` abstraction. module game -intrude import json::store +import mongodb import github::events # An entity within a `Game`. @@ -35,23 +35,21 @@ interface GameEntity # 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 @@ -74,18 +72,30 @@ class Game # 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. # @@ -118,10 +128,12 @@ class Game # 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. @@ -130,12 +142,12 @@ class Game # # 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 @@ -154,7 +166,7 @@ class Game 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. # @@ -186,6 +198,9 @@ end class Player super GameEntity + # Stored in collection `players`. + redef var collection_name = "players" + redef var game # FIXME contructor should be private @@ -217,7 +232,7 @@ class Player # # 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 diff --git a/contrib/nitrpg/src/statistics.nit b/contrib/nitrpg/src/statistics.nit index 153b5cd..0bdb421 100644 --- a/contrib/nitrpg/src/statistics.nit +++ b/contrib/nitrpg/src/statistics.nit @@ -34,11 +34,6 @@ redef class Game 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 @@ -46,17 +41,17 @@ redef class Game 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 @@ -67,6 +62,11 @@ redef class Player 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. @@ -99,12 +99,15 @@ class GameStatsManager # 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] @@ -133,12 +136,12 @@ class GameStatsManager 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 @@ -151,6 +154,8 @@ class GameStats redef var game + redef var collection_name = "statistics" + # The period these stats are about. var period: String diff --git a/contrib/nitrpg/src/templates/templates_base.nit b/contrib/nitrpg/src/templates/templates_base.nit index a4f40d9..470160b 100644 --- a/contrib/nitrpg/src/templates/templates_base.nit +++ b/contrib/nitrpg/src/templates/templates_base.nit @@ -21,8 +21,11 @@ import achievements 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 @@ -32,7 +35,7 @@ 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 "{name}" diff --git a/contrib/nitrpg/src/test_achievements.nit b/contrib/nitrpg/src/test_achievements.nit new file mode 100644 index 0000000..b5d4cb7 --- /dev/null +++ b/contrib/nitrpg/src/test_achievements.nit @@ -0,0 +1,145 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# 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 diff --git a/contrib/nitrpg/src/test_events.nit b/contrib/nitrpg/src/test_events.nit new file mode 100644 index 0000000..83f17bf --- /dev/null +++ b/contrib/nitrpg/src/test_events.nit @@ -0,0 +1,144 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# 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 diff --git a/contrib/nitrpg/src/test_game.nit b/contrib/nitrpg/src/test_game.nit new file mode 100644 index 0000000..78f3bd0 --- /dev/null +++ b/contrib/nitrpg/src/test_game.nit @@ -0,0 +1,146 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# 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 diff --git a/contrib/nitrpg/src/test_helper.nit b/contrib/nitrpg/src/test_helper.nit new file mode 100644 index 0000000..d8ba14e --- /dev/null +++ b/contrib/nitrpg/src/test_helper.nit @@ -0,0 +1,54 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# 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 diff --git a/contrib/nitrpg/src/test_listener.nit b/contrib/nitrpg/src/test_listener.nit new file mode 100644 index 0000000..edbfe37 --- /dev/null +++ b/contrib/nitrpg/src/test_listener.nit @@ -0,0 +1,369 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# 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 diff --git a/contrib/nitrpg/src/test_statistics.nit b/contrib/nitrpg/src/test_statistics.nit new file mode 100644 index 0000000..aa4c893 --- /dev/null +++ b/contrib/nitrpg/src/test_statistics.nit @@ -0,0 +1,83 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# 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 diff --git a/contrib/nitrpg/src/web.nit b/contrib/nitrpg/src/web.nit index 38ed68e..f624f48 100644 --- a/contrib/nitrpg/src/web.nit +++ b/contrib/nitrpg/src/web.nit @@ -57,8 +57,8 @@ class RpgAction # 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 @@ -66,13 +66,16 @@ class RpgAction # 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 diff --git a/lib/pthreads/concurrent_collections.nit b/lib/pthreads/concurrent_collections.nit index 67e6765..d55bad0 100644 --- a/lib/pthreads/concurrent_collections.nit +++ b/lib/pthreads/concurrent_collections.nit @@ -499,4 +499,12 @@ class ConcurrentList[E] real_collection.push(e) mutex.unlock end + + redef fun shift + do + mutex.lock + var value = real_collection.shift + mutex.unlock + return value + end end diff --git a/lib/pthreads/examples/jointask_example.nit b/lib/pthreads/examples/jointask_example.nit new file mode 100644 index 0000000..3d12f6f --- /dev/null +++ b/lib/pthreads/examples/jointask_example.nit @@ -0,0 +1,52 @@ +# 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 diff --git a/lib/pthreads/examples/threadpool_example.nit b/lib/pthreads/examples/threadpool_example.nit index f7fafd7..fec1ae2 100644 --- a/lib/pthreads/examples/threadpool_example.nit +++ b/lib/pthreads/examples/threadpool_example.nit @@ -19,7 +19,7 @@ import threadpool # Task printing "hello world" on standard output class HWTask - super Task + super JoinTask # Sleeping time var sec: Int diff --git a/lib/pthreads/threadpool.nit b/lib/pthreads/threadpool.nit index 42931d3..02042ba 100644 --- a/lib/pthreads/threadpool.nit +++ b/lib/pthreads/threadpool.nit @@ -20,7 +20,7 @@ import concurrent_collections # 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 @@ -37,7 +37,7 @@ class ThreadPool 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 @@ -47,20 +47,49 @@ 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 diff --git a/tests/sav/jointask_example.res b/tests/sav/jointask_example.res new file mode 100644 index 0000000..a0f7bca --- /dev/null +++ b/tests/sav/jointask_example.res @@ -0,0 +1,6 @@ +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