module shibuqamoauth
import popcorn
+import popcorn::pop_json
import shibuqam
import json
else
print "Looking for a server..."
- var s = new UDPSocket
- s.enable_broadcast = true
- s.blocking = false
- s.broadcast(discovery_port, "Server? {handshake_app_name}")
- nanosleep(0, 100_000_000)
-
- var ptr = new Ref[nullable SocketAddress](null)
- var resp = s.recv_from(1024, ptr)
- var src = ptr.item
-
- if not resp.is_empty then
- var words = resp.split(" ")
- if words.length == 3 and words[0] == "Server!" and words[1] == handshake_app_name and words[2].is_numeric then
- address = src.address
- port = words[2].to_i
- end
+ var servers = discover_local_servers
+ if servers.not_empty then
+ address = servers.first.address
+ port = servers.first.port
end
end
# No command line
return new LocalServerContext
else
- print "Connecting to:{address}:{port}"
+ print "Connecting to {address}:{port}"
# Args are: tinks server_address {port}
if args.length > 1 then port = args[1].to_i
# Default listening port of the server
fun default_listening_port: Int do return 18721
- # Port to which clients send discovery requests
- fun discovery_port: Int do return 18722
+ redef fun discovery_port do return 18722
end
end
redef class Server
- # `UDPSocket` to which clients send discovery requests
- var discovery_socket: UDPSocket do
- var s = new UDPSocket
- s.blocking = false
- s.bind(null, discovery_port)
- return s
- end
# The current game
var game = new TGame is lazy, writable
# Do game logic
var turn = game.do_turn
- # Respond to discovery requests
- loop
- var ptr = new Ref[nullable SocketAddress](null)
- var read = discovery_socket.recv_from(1024, ptr)
-
- # No sender means there is no request (an error would also do it)
- var sender = ptr.item
- if sender == null then break
-
- var words = read.split(" ")
- if words.length != 2 or words[0] != "Server?" or words[1] != handshake_app_name then
- print "Server Warning: Rejected discovery request '{read}'"
- continue
- end
-
- discovery_socket.send_to(sender.address, sender.port,
- "Server! {handshake_app_name} {self.port}")
- end
+ # Respond to discovery requests sent over UDP
+ answer_discovery_requests
# Setup clients
var new_clients = accept_clients
client.writer.serialize game
client.writer.serialize client.player
client.socket.flush
-
- clients.add client
end
if dedicated and clients.is_empty then
new_annotation actor
end
-import pthreads::concurrent_collections
+intrude import pthreads::concurrent_collections
intrude import pthreads
intrude import pthreads::extra
var instance: V
# Mailbox used to receive and process messages
- var mailbox = new BlockingQueue[Message].with_actor(self)
+ var mailbox = new Mailbox[Message].with_actor(self)
# Is `self` working ?
# i.e. does it have messages to process or is it processing one now ?
- var working = false
+ var working = true
redef fun main do
loop
var m = mailbox.shift
if m isa ShutDownMessage then
- sys.active_actors.remove(self)
+ sys.active_actors.decrement
return null
end
m.invoke(instance)
- if mailbox.is_empty then
- working = false
- sys.active_actors.remove(self)
- end
end
end
end
end
+# A Blocking queue implemented from a `ConcurrentList`
+# `shift` is blocking if there isn't any element in `self`
+# `push` or `unshift` releases every blocking threads
+# Corresponds to the mailbox of an actor
+class Mailbox[E]
+ super BlockingQueue[E]
+
+ # The associated actor
+ var actor: Actor is noautoinit
+
+ # init self with an associated actor
+ init with_actor(actor: Actor) do
+ self.actor = actor
+ sys.active_actors.increment
+ end
+
+ # Adding the signal to release eventual waiting thread(s)
+ redef fun push(e) do
+ mutex.lock
+ if real_collection.is_empty and not actor.working then
+ actor.working = true
+ sys.active_actors.increment
+ real_collection.push(e)
+ self.cond.signal
+ else
+ real_collection.push(e)
+ end
+ mutex.unlock
+ end
+
+ redef fun unshift(e) do
+ mutex.lock
+ real_collection.unshift(e)
+ self.cond.signal
+ mutex.unlock
+ end
+
+ # If empty, blocks until an item is inserted with `push` or `unshift`
+ redef fun shift do
+ mutex.lock
+ if real_collection.is_empty then
+ actor.working = false
+ sys.active_actors.decrement
+ while real_collection.is_empty do self.cond.wait(mutex)
+ end
+ var r = real_collection.shift
+ mutex.unlock
+ return r
+ end
+
+ redef fun is_empty do
+ mutex.lock
+ var r = real_collection.is_empty
+ mutex.unlock
+ return r
+ end
+end
+
# A Message received by a Mailbox
# In fact, this is the reification of a call
# Each Message class represent a call to make on `instance` via `invoke`
end
end
-# A Blocking queue implemented from a `ConcurrentList`
-# `shift` is blocking if there isn't any element in `self`
-# `push` or `unshift` releases every blocking threads
-# Corresponds to the mailbox of an actor
-class BlockingQueue[E]
- super ConcurrentList[E]
-
- # The associated actor
- var actor: Actor is noautoinit
-
- # Used to block or signal on waiting threads
- private var cond = new PthreadCond
-
- # init self with an associated actor
- init with_actor(actor: Actor) do self.actor = actor
-
- # Adding the signal to release eventual waiting thread(s)
- redef fun push(e) do
- mutex.lock
- if real_collection.is_empty and not actor.working then
- actor.working = true
- sys.active_actors.push(actor)
- end
- real_collection.push(e)
- self.cond.signal
- mutex.unlock
- end
-
- redef fun unshift(e) do
- mutex.lock
- real_collection.unshift(e)
- self.cond.signal
- mutex.unlock
- end
+# A counter on which threads can wait until its value is 0
+class SynchronizedCounter
- # If empty, blocks until an item is inserted with `push` or `unshift`
- redef fun shift do
- mutex.lock
- while real_collection.is_empty do self.cond.wait(mutex)
- var r = real_collection.shift
- mutex.unlock
- return r
- end
-end
+ # The starting value, always starts with 0
+ private var c = 0
-# A collection which `is_empty` method blocks until it's empty
-class ReverseBlockingQueue[E]
- super ConcurrentList[E]
-
- # Used to block or signal on waiting threads
private var cond = new PthreadCond
+ private var mutex = new Mutex
- # Adding the signal to release eventual waiting thread(s)
- redef fun push(e) do
+ # Increment the counter atomically
+ fun increment do
mutex.lock
- real_collection.push(e)
+ c += 1
mutex.unlock
end
- # When the Queue is empty, signal any
- # possible waiting thread
- redef fun remove(e) do
+ # Decrement the counter atomically,
+ # signals to waiting thread(s) if `c == 0`
+ fun decrement do
mutex.lock
- real_collection.remove(e)
- if real_collection.is_empty then cond.signal
+ c -= 1
+ if c == 0 then
+ cond.signal
+ end
mutex.unlock
end
- # Wait until the Queue is empty
- redef fun is_empty do
+ # Block until `c == 0`
+ fun wait do
mutex.lock
- while not real_collection.is_empty do self.cond.wait(mutex)
+ while c != 0 do cond.wait(mutex)
mutex.unlock
- return true
end
end
redef class Sys
- # List of running actors
- var active_actors = new ReverseBlockingQueue[Actor] is lazy
+ # Number of active actors
+ var active_actors = new SynchronizedCounter
redef fun run do
super
# The program won't end until every actor is done
- active_actors.is_empty
+ active_actors.wait
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.
+
+# a "Framework" to make Multi-Agent Simulations in Nit
+module agent_simulation is no_warning("missing-doc")
+
+import actors
+
+# Master of the simulation, it initiates the steps and waits for them to terminate
+class ClockAgent
+ actor
+
+ # Number of steps to do in the simulation
+ var nb_steps: Int
+
+ # List of Agents in the simulation
+ var agents: Array[Agent]
+
+ # Number of agents that finished their step
+ var nb_finished = 0
+
+ fun do_step do
+ for a in agents do a.async.do_step
+ nb_steps -= 1
+ end
+
+ fun finished_step do
+ nb_finished += 1
+ if nb_finished == agents.length then
+ nb_finished = 0
+ if nb_steps != 0 then async.do_step
+ end
+ end
+end
+
+class Agent
+ actor
+
+ # Doing a step in the simulation
+ fun do_step do
+ end
+
+ fun end_step do clock_agent.async.finished_step
+
+end
+
+redef class Sys
+ var clock_agent: ClockAgent is noautoinit,writable
+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.
+
+# Using `agent_simulation` by refining the Agent class to make
+# a multi-agent simulation where every agent know each other
+# The steps consist of each agent greeting each other, and
+# waiting for every other agent to respond before notifying
+# to the `ClockAgent` that they finished their step.
+module simple_simulation
+
+import agent_simulation
+
+redef class Agent
+ var others = new Array[Agent]
+ var count = 0
+
+ fun greet(message: String, other: Agent) do other.async.greet_back("Hello back !")
+
+ fun greet_back(message: String) do
+ count -= 1
+ if count == 0 then end_step
+ end
+
+ redef fun do_step do
+ for o in others do
+ o.async.greet("Hello !", self)
+ count += 1
+ end
+ end
+end
+
+var nb_steps = 0
+var nb_agents = 0
+if args.is_empty or args.length != 2 then
+ nb_steps = 10
+ nb_agents = 10
+else
+ nb_steps = args[0].to_i
+ nb_agents = args[1].to_i
+end
+
+var agents = new Array[Agent]
+for i in [0..nb_agents[ do agents.add(new Agent)
+for a in agents do for b in agents do if a != b then a.others.add(b)
+clock_agent = new ClockAgent(nb_steps, agents)
+clock_agent.async.do_step
for c in creatures do c.async.run
- active_actors.is_empty
+ active_actors.wait
var total = 0
for c in creatures do
# ~~~
module client
-import common
+intrude import common
# Information of the remove server
class RemoteServerConfig
return true
end
end
+
+# Discover local servers responding on UDP `discovery_port`
+#
+# Sends a message in the format `gamnit::network? handshake_app_name` and
+# looks for the response `gamnit::network! handshake_app_name port_number`.
+# Waits for `timeout`, or the default 0.1 seconds, after sending the message.
+#
+# The server usually responds using the method `answer_discovery_requests`.
+# When receiving responses, the client may then choose a server and
+# connect via `new RemoteServer`.
+#
+# ~~~
+# var servers = discover_local_servers
+# if servers.not_empty then
+# var server = new RemoteServer(servers.first)
+# server.connect
+# server.writer.serialize "hello server"
+# server.socket.close
+# end
+# ~~~
+fun discover_local_servers(timeout: nullable Float): Array[RemoteServerConfig]
+do
+ timeout = timeout or else 0.1
+
+ var s = new UDPSocket
+ s.enable_broadcast = true
+ s.blocking = false
+ s.broadcast(discovery_port, "{discovery_request_message} {handshake_app_name}")
+ timeout.sleep
+
+ var r = new Array[RemoteServerConfig]
+ loop
+ var ptr = new Ref[nullable SocketAddress](null)
+ var resp = s.recv_from(1024, ptr)
+ var src = ptr.item
+
+ if resp.is_empty then
+ # No response
+ break
+ else
+ assert src != null
+ var words = resp.split(" ")
+ if words.length == 3 and words[0] == discovery_response_message and
+ words[1] == handshake_app_name and words[2].is_int then
+ var address = src.address
+ var port = words[2].to_i
+ r.add new RemoteServerConfig(address, port)
+ end
+ end
+ end
+ return r
+end
#
# This name must be the same between client/server and
# it should not be used by other programs that may interfere.
-#
# Both client and server refuse connections with a different name.
+#
+# This value must not contain spaces.
fun handshake_app_name: String do return program_name
# Version of the communication protocol to use in the handshake
# that different versions indicates incompatible protocols.
#
# Both client and server refuse connections with a different version.
+#
+# This value must not contain spaces.
fun handshake_app_version: String do return "0.0"
+
+# Server port listening for discovery requests
+#
+# This name must be the same between client/server.
+fun discovery_port: Int do return 18722
+
+# First word in discovery requests
+private fun discovery_request_message: String do return "gamnit::network?"
+
+# First word in discovery responses
+private fun discovery_response_message: String do return "gamnit::network!"
# limitations under the License.
# Easy client/server logic for games and simple distributed applications
+#
+# Both `gamnit::client` and `gamnit::server` can be used separately or
+# together by importing `gamnit::network`.
+# Use both modules to create an program that discover local servers
+# or create one if none is found:
+#
+# ~~~
+# redef fun handshake_app_name do return "network_test"
+#
+# # Discover local servers
+# var servers = discover_local_servers
+# if servers.not_empty then
+# # Try to connect to the first local server
+# var server_info = servers.first
+# var server = new RemoteServer(server_info)
+#
+# if not server.connect then
+# print_error "Failed to connect to {server_info.address}:{server_info.port}"
+# else if not server.handshake then
+# print_error "Failed handshake with {server_info.address}:{server_info.port}"
+# else
+# # Connected!
+# print "Connected to {server_info.address}:{server_info.port}"
+#
+# # Write something and close connection
+# server.writer.serialize "hello server"
+# server.socket.as(not null).close
+# end
+# else
+# # Create a local server
+# var connect_port = 33729
+# print "Launching server: connect on {connect_port}, discovery on {discovery_port}"
+# var server = new Server(connect_port)
+#
+# # Don't loop if testing
+# if "NIT_TESTING".environ == "true" then exit 0
+#
+# loop
+# # Respond to discovery requests
+# server.answer_discovery_requests
+#
+# # Accept new clients
+# var new_clients = server.accept_clients
+# for client in new_clients do
+# # Read something and close connection
+# assert client.reader.deserialize == "hello server"
+# client.socket.close
+# end
+# end
+# end
+# ~~~
module network
import server
#
# # `accept_clients` in non-blocking,
# # sleep before tying again, or do something else.
-# nanosleep(0, 50000000)
+# 0.5.sleep
# printn "."
# end
# ~~~
module server
-import common
+intrude import common
# Game server controller
class Server
# All connected `RemoteClient`
var clients = new Array[RemoteClient]
- # Socket accepting new connections
+ # TCP socket accepting new connections
+ #
+ # Opened on the first call to `accept_clients`.
var listening_socket: TCPServer is lazy do
- print port
var socket = new TCPServer(port)
socket.listen 8
socket.blocking = false
return socket
end
- init do listening_socket
# Accept currently waiting clients and return them as an array
- fun accept_clients: Array[RemoteClient]
+ #
+ # If `add_to_clients`, the default, the new clients are added to `clients`.
+ # Otherwise, the return value of `accept_clients` may be added to `clients`
+ # explicitly by the caller after an extra verification or sorting.
+ fun accept_clients(add_to_clients: nullable Bool): Array[RemoteClient]
do
+ add_to_clients = add_to_clients or else true
assert not listening_socket.closed
var new_clients = new Array[RemoteClient]
client_socket.close
end
end
+
+ if add_to_clients then clients.add_all new_clients
+
return new_clients
end
# Broadcast a `message` to all `clients`, then flush the connection
- fun broadcast(message: Serializable)
+ #
+ # The client `except` is skipped and will not receive the `message`.
+ fun broadcast(message: Serializable, except: nullable RemoteClient)
do
- for client in clients do
+ for client in clients do if client != except then
client.writer.serialize(message)
client.socket.flush
end
end
+
+ # Respond to pending discovery requests by sending the TCP listening address and port
+ #
+ # Returns the number of valid requests received.
+ #
+ # The response messages includes the TCP listening address and port
+ # for remote clients to connect with TCP using `connect`.
+ # These connections are accepted by the server with `accept_clients`.
+ fun answer_discovery_requests: Int
+ do
+ var count = 0
+ loop
+ var ptr = new Ref[nullable SocketAddress](null)
+ var read = discovery_socket.recv_from(1024, ptr)
+
+ # No sender means there is no discovery request
+ var sender = ptr.item
+ if sender == null then break
+
+ var words = read.split(" ")
+ if words.length != 2 or words[0] != discovery_request_message or words[1] != handshake_app_name then
+ print "Server Warning: Rejected discovery request '{read}' from {sender.address}:{sender.port}"
+ continue
+ end
+
+ var msg = "{discovery_response_message} {handshake_app_name} {self.port}"
+ discovery_socket.send_to(sender.address, sender.port, msg)
+ count += 1
+ end
+ return count
+ end
+
+ # UDP socket responding to discovery requests
+ #
+ # Usually opened on the first call to `answer_discovery_request`.
+ var discovery_socket: UDPSocket is lazy do
+ var s = new UDPSocket
+ s.blocking = false
+ s.bind(null, discovery_port)
+ return s
+ end
end
# Reference to a remote client connected to this server
# Check for compatibility with the client
fun handshake: Bool
do
- print "Server: Handshake requested by {socket.address}"
+ print "Server: Handshake initiated by {socket.address}"
# Make sure it is the same app
var server_app = sys.handshake_app_name
# limitations under the License.
import popcorn
+import popcorn::pop_json
class CounterAPI
super Handler
# ~~~
module pop_auth
+import pop_json
import pop_sessions
import github
module pop_handlers
import pop_routes
-import json::static
-import json
import csv
# Class handler for a route.
send(html, status)
end
- # Write data as JSON and set the right content type header.
- fun json(json: nullable Serializable, status: nullable Int) do
- header["Content-Type"] = media_types["json"].as(not null)
- if json == null then
- send(null, status)
- else
- send(json.to_json, status)
- end
- end
-
# Write data as CSV and set the right content type header.
fun csv(csv: nullable CsvDocument, status: nullable Int) do
header["Content-Type"] = media_types["csv"].as(not null)
end
end
- # Write error as JSON.
- #
- # Format: `{"message": message, "status": status}`
- fun json_error(message: String, status: Int) do
- var obj = new JsonObject
- obj["status"] = status
- obj["message"] = message
- json(obj, status)
- end
-
# Redirect response to `location`
#
# Use by default 303 See Other as it is the RFC
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2017 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.
+
+# Introduce useful services for JSON REST API handlers.
+#
+# Validation and Deserialization of request bodies:
+#
+# ~~~nit
+# class MyJsonHandler
+# super Handler
+#
+# # Validator used do validate the body
+# redef var validator = new MyFormValidator
+#
+# # Define the kind of objects expected by the deserialization process
+# redef type BODY: MyForm
+#
+# redef fun post(req, res) do
+# var post = validate_body(req, res)
+# if post == null then return # Validation error: let popcorn return a HTTP 400
+# var form = deserialize_body(req, res)
+# if form == null then return # Deserialization error: let popcorn return a HTTP 400
+#
+# # TODO do something with the input
+# print form.name
+# end
+# end
+#
+# class MyForm
+# serialize
+#
+# var name: String
+# end
+#
+# class MyFormValidator
+# super ObjectValidator
+#
+# init do
+# add new StringField("name", min_size=1, max_size=255)
+# end
+# end
+# ~~~
+module pop_json
+
+import json
+import pop_handlers
+import pop_validation
+
+redef class Handler
+
+ # Validator used to check body input
+ #
+ # Here we use the `pop_validation` module to validate JSON document from the request body.
+ var validator: nullable DocumentValidator = null
+
+ # Validate body input with `validator`
+ #
+ # Try to validate the request body as a json document using `validator`:
+ # * Returns the validated string input if the result of the validation is ok.
+ # * Answers a json error and returns `null` if something went wrong.
+ # * If no `validator` is set, returns the body without validation.
+ #
+ # Example:
+ #
+ # ~~~nit
+ # class ValidatedHandler
+ # super Handler
+ #
+ # redef var validator = new MyObjectValidator
+ #
+ # redef fun post(req, res) do
+ # var body = validate_body(req, res)
+ # if body == null then return # Validation error
+ # # At this point popcorn returned a HTTP 400 code with the validation error
+ # # if the validation failed.
+ #
+ # # TODO do something with the input
+ # print body
+ # end
+ # end
+ #
+ # class MyObjectValidator
+ # super ObjectValidator
+ #
+ # init do
+ # add new StringField("name", min_size=1, max_size=255)
+ # end
+ # end
+ # ~~~
+ fun validate_body(req: HttpRequest, res: HttpResponse): nullable String do
+ var body = req.body
+
+ var validator = self.validator
+ if validator == null then return body
+
+ if not validator.validate(body) then
+ res.json(validator.validation, 400)
+ return null
+ end
+ return body
+ end
+
+ # Deserialize the request body
+ #
+ # Returns the deserialized request body body or `null` if something went wrong.
+ # If the object cannot be deserialized, answers with a HTTP 400.
+ #
+ # See `BODY` and `new_body_object`.
+ #
+ # Example:
+ # ~~~nit
+ # class MyDeserializedHandler
+ # super Handler
+ #
+ # redef type BODY: MyObject
+ #
+ # redef fun post(req, res) do
+ # var form = deserialize_body(req, res)
+ # if form == null then return # Deserialization error
+ # # At this point popcorn returned a HTTP 400 code if something was wrong with
+ # # the deserialization process
+ #
+ # # TODO do something with the input
+ # print form.name
+ # end
+ # end
+ #
+ # class MyObject
+ # serialize
+ #
+ # var name: String
+ # end
+ # ~~~
+ fun deserialize_body(req: HttpRequest, res: HttpResponse): nullable BODY do
+ var body = req.body
+ var deserializer = new JsonDeserializer(body)
+ var form = deserializer.deserialize(body)
+ if not form isa BODY or deserializer.errors.not_empty then
+ res.json_error("Bad input", 400)
+ return null
+ end
+ return form
+ end
+
+ # Kind of objects returned by `deserialize_body`
+ #
+ # Define it in each sub handlers depending on the kind of objects sent in request bodies.
+ type BODY: Serializable
+end
+
+redef class HttpResponse
+
+ # Write data as JSON and set the right content type header.
+ fun json(json: nullable Serializable, status: nullable Int) do
+ header["Content-Type"] = media_types["json"].as(not null)
+ if json == null then
+ send(null, status)
+ else
+ send(json.to_json, status)
+ end
+ end
+
+ # Write error as JSON.
+ #
+ # Format: `{"message": message, "status": status}`
+ fun json_error(message: String, status: Int) do
+ var obj = new JsonObject
+ obj["status"] = status
+ obj["message"] = message
+ json(obj, status)
+ end
+end
#
# import popcorn
# import popcorn::pop_repos
+# import popcorn::pop_json
#
# # Serializable book representation.
# class Book
# ~~~
# import popcorn
# import popcorn::pop_repos
+# import popcorn::pop_json
#
# # First, let's create a User abstraction:
#
module pop_templates
import popcorn::pop_handlers
+import popcorn::pop_json
import template::macro
redef class HttpResponse
import popcorn
import popcorn::pop_config
import popcorn::pop_logging
+import popcorn::pop_json
import popcorn::pop_repos
redef class AppConfig
#
# ~~~
# import popcorn
+# import popcorn::pop_json
# import serialization
#
# # Serializable book representation.
return value
end
end
+
+# A collection which `is_empty` method blocks until it's empty
+class ReverseBlockingQueue[E]
+ super ConcurrentList[E]
+
+ # Used to block or signal on waiting threads
+ private var cond = new PthreadCond
+
+ # Adding the signal to release eventual waiting thread(s)
+ redef fun push(e) do
+ mutex.lock
+ real_collection.push(e)
+ mutex.unlock
+ end
+
+ # When the Queue is empty, signal any possible waiting thread
+ redef fun remove(e) do
+ mutex.lock
+ real_collection.remove(e)
+ if real_collection.is_empty then cond.signal
+ mutex.unlock
+ end
+
+ # Wait until the Queue is empty
+ redef fun is_empty do
+ mutex.lock
+ while not real_collection.is_empty do self.cond.wait(mutex)
+ mutex.unlock
+ return true
+ end
+end
+
+# A Blocking queue implemented from a `ConcurrentList`
+# `shift` is blocking if there isn't any element in `self`
+# `push` or `unshift` releases every blocking threads
+class BlockingQueue[E]
+ super ConcurrentList[E]
+
+ # Used to block or signal on waiting threads
+ private var cond = new PthreadCond
+
+ # Adding the signal to release eventual waiting thread(s)
+ redef fun push(e) do
+ mutex.lock
+ real_collection.push(e)
+ self.cond.signal
+ real_collection.push(e)
+ mutex.unlock
+ end
+
+ redef fun unshift(e) do
+ mutex.lock
+ real_collection.unshift(e)
+ self.cond.signal
+ mutex.unlock
+ end
+
+ # If empty, blocks until an item is inserted with `push` or `unshift`
+ redef fun shift do
+ mutex.lock
+ while real_collection.is_empty do self.cond.wait(mutex)
+ var r = real_collection.shift
+ mutex.unlock
+ return r
+ end
+
+ redef fun is_empty do
+ mutex.lock
+ var r = real_collection.is_empty
+ mutex.unlock
+ return r
+ end
+end
# See `nit/lib/actors/actors.nit` for the abstraction on which the generated classes are based
module actors_generation_phase
+import actors_injection_phase
import modelize
import gen_nit
import modelbuilder
private class ActorPhase
super Phase
+ var generated_actor_modules = new Array[String]
+
# Source code of the actor classes to generate
var actors = new Array[String]
# Redefinitions of annotated classes
var redef_classes = new Array[String]
- redef fun process_annotated_node(nclass, nat)
- do
- if nat.n_atid.n_id.text != "actor" then return
-
- if not nclass isa AStdClassdef then
- toolcontext.error(nat.location, "Syntax Error: only a class can be annotated as an actor.")
- return
- end
-
- # Get the module associated with this class
- var mclassdef = nclass.mclassdef
- assert mclassdef != null
-
- var mmod = mclassdef.mmodule
+ fun generate_actor_classes(mclassdef: MClassDef, mmod: MModule) do
if not mmod.generate_actor_submodule then mmod.generate_actor_submodule = true
# Get the name of the annotated class
var classname = mclassdef.name
# Generate the actor class
- actors.add(
+ if mclassdef.is_intro then actors.add(
"""
class Actor{{{classname}}}
super Actor
######## Generate the Messages classes ########
# Get all the methods definitions
- var propdefs = mclassdef.mpropdefs
+ var propdefs = new Array[MPropDef]
+ for propdef in mclassdef.mpropdefs do
+ if propdef.is_intro then propdefs.add(propdef)
+ end
+
var methods = new Array[MMethodDef]
for p in propdefs do
if p isa MMethodDef then
# Generate the superclass for all Messages classes (each actor has its own Message super class)
var msg_class_name = "Message" + classname
- messages.add(
+
+ if mclassdef.is_intro then messages.add(
"""
class {{{msg_class_name}}}
super Message
# All of the functions of the proxy too
# Let's generate the proxy class then
+
+ var redef_virtual_type = ""
+ if mclassdef.is_intro then redef_virtual_type = "redef type E: Actor{classname}"
proxys.add(
"""
redef class Proxy{{{classname}}}
- redef type E: Actor{{{classname}}}
- #var actor: Actor{{{classname}}} is noinit
+ {{{redef_virtual_type}}}
init proxy(base_class: {{{classname}}}) do
actor = new Actor{{{classname}}}(base_class)
end
""")
- redef_classes.add(
+ if mclassdef.is_intro then redef_classes.add(
"""
redef class {{{classname}}}
-redef var async is lazy do return new Proxy{{{classname}}}.proxy(self)
+ var m = new Mutex
+ var lazy_proxy: Proxy{{{classname}}} is lazy do return new Proxy{{{classname}}}.proxy(self)
+
+ redef fun async: Proxy{{{classname}}} do
+ m.lock
+ var p = lazy_proxy
+ m.unlock
+ return p
+ end
end
""")
+
+ end
+
+ redef fun process_nmodule(nmodule) do
+ var mmod = nmodule.mmodule
+ if mmod == null then return
+
+ if generated_actor_modules.has(mmod.name) then return
+
+ var mclasses_defs = mmod.mclassdefs
+ for mclass_def in mclasses_defs do
+ var mclass = mclass_def.mclass
+ var actor = mclass.actor
+ if actor != null then generate_actor_classes(mclass_def, mmod)
+ end
+
+ end
+
+ redef fun process_annotated_node(nclass, nat)
+ do
+ if nat.n_atid.n_id.text != "actor" then return
+
+ if not nclass isa AStdClassdef then
+ toolcontext.error(nat.location, "Syntax Error: only a class can be annotated as an actor.")
+ return
+ end
end
redef fun process_nmodule_after(nmodule) do
# for mmod in mmodules do nit_module.imports.add mmod.name
nit_module.imports.add first_mmodule.name
+ generated_actor_modules.add(module_name)
+ var idx = generated_actor_modules.index_of(module_name)
+ for i in [0..idx[ do nit_module.imports.add(generated_actor_modules[i])
+
nit_module.content.add "####################### Redef classes #########################"
for c in redef_classes do nit_module.content.add( c + "\n\n" )
# Write support module
nit_module.write_to_file module_path
- actors = new Array[String]
- messages = new Array[String]
- proxys = new Array[String]
- redef_classes = new Array[String]
+ actors.clear
+ messages.clear
+ proxys.clear
+ redef_classes.clear
toolcontext.modelbuilder.inject_module_subimportation(first_mmodule, module_path)
end
redef class ToolContext
# Generate actors
- var actor_phase: Phase = new ActorPhase(self, [modelize_class_phase])
+ var actor_phase: Phase = new ActorPhase(self, [modelize_class_phase, modelize_property_phase])
end
res.append "::"
end
end
- if mclassdef.mclass != mproperty.intro_mclassdef.mclass then
- # precise "B" only if not the same class than "A"
- res.append mproperty.intro_mclassdef.name
- res.append "::"
- end
+ # precise "B" because it is not the same class than "A"
+ res.append mproperty.intro_mclassdef.name
+ res.append "::"
# Always use the property name "x"
res.append mproperty.name
end
res.append mproperty.intro_mclassdef.mmodule.c_name
res.append "__"
end
- if mclassdef.mclass != mproperty.intro_mclassdef.mclass then
- res.append mproperty.intro_mclassdef.name.to_cmangle
- res.append "__"
- end
+ res.append mproperty.intro_mclassdef.name.to_cmangle
+ res.append "__"
res.append mproperty.name.to_cmangle
end
return res.to_s
bounds.add(bound)
nfd.bound = bound
end
- if bound isa MClassType and bound.mclass.kind == enum_kind then
- warning(nfdt, "useless-bound", "Warning: useless formal parameter type since `{bound}` cannot have subclasses.")
- end
else if mclass.mclassdefs.is_empty then
if objectclass == null then
error(nfd, "Error: formal parameter type `{pname}` unbounded but no `Object` class exists.")
# Root of the AST class-hierarchy
abstract class ANode
- # Location is set during AST building. Once built, location cannon be null.
+ # Location is set during AST building. Once built, location can not be null.
# However, manual instantiated nodes may need more care.
var location: Location is writable, noinit
super TokenKeyword
end
-# The keywords `enum` ane `universal`
+# The keywords `enum` and `universal`
class TKwenum
super TokenKeyword
end
super Prod
# The identifier of the annotation.
- # Can be a TId of a keyword
+ #
+ # Can be a TId or a keyword.
var n_id: Token is writable, noinit
end
import popcorn
import popcorn::pop_config
import popcorn::pop_repos
+import popcorn::pop_json
# Nitweb config file.
class NitwebConfig
+++ /dev/null
-base_gen_final_bound.nit:23,16--18: Warning: useless formal parameter type since `Int` cannot have subclasses.
-alt/error_class_generic_alt2.nit:18,22--26: Warning: useless formal parameter type since `Float` cannot have subclasses.
alt/error_class_generic_alt2.nit:25,8--12: Type Error: `Array[E: nullable Object]` is a generic class.
-test_gen_inh.nit:29,15--17: Warning: useless formal parameter type since `Int` cannot have subclasses.
-test_gen_inh.nit:34,15--17: Warning: useless formal parameter type since `Int` cannot have subclasses.
11
22
33
-test_super_gen.nit:27,12--14: Warning: useless formal parameter type since `Int` cannot have subclasses.
1
0
5
-test_super_gen.nit:27,12--14: Warning: useless formal parameter type since `Int` cannot have subclasses.
-test_super_gen_raf.nit:19,12--14: Warning: useless formal parameter type since `Int` cannot have subclasses.
0
20