This repository has been archived by the owner on Sep 2, 2020. It is now read-only.
/
net.coffee
122 lines (93 loc) · 5.11 KB
/
net.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
###
Orona, © 2010 Stéphan Kochen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
###
# Orona uses two WebSocket connections during play. The first is the lobby connection, which is
# always open, and is also used for in-game chat. The second is used for world synchronization,
# which is kept separate so that the lobby connection cannot impede network performance of game
# updates (or at least diminish the effect).
# The collecting of data of world updates is governed by this module. World updates are split up
# in two kinds of messages.
# The first are critical updates, which are object creation an destruction. Both the server and
# client have lists of objects that are kept in sync. In order to do that, these updates are
# transmitted reliably to clients. (But actual transport is not done by this module.)
# The second are attribute updates for existing objects. A single update message of this kind
# (currently) contains a complete set of updates for all world objects. There are no differential
# updates, so it's okay for the underlying transport to drop some of these.
# In order to do the right thing in different situations without complicating simulation code,
# a global networking context object is used that handles all networking state. The simulation
# only calls into a couple of methods, and is ignorant of what happens from there.
# FIXME: still missing here is synchronization of pills and bases.
# Perhaps those should be objects too?
# The interface provided by world objects. Unused, but here for documentation.
class WorldObject
# An alternate constructor used to instantiate an object when it is created by the networking
# code. This call will be immediately followed by a call to deserialize. Note that this method
# MUST return `this`!
constructFromNetwork: (game) ->
# This method should return an array of bytes that represent the object's state somehow.
# It usually calls struct.pack() to pack all of it's state variables.
serialize: ->
# This methods takes an array of bytes and an optional offset, at which to find data originally
# generated by serialize. This method is then responsible for translating that data back into
# object state, usually calling struct.unpack() to do so. Finally, it returns the number of
# bytes it used.
deserialize: (data, offset) ->
# The interface provided by network contexts. Unused, but here for documentation.
class Context
constructor: (game) ->
# Called when the context is activated. See 'inContext' for more information.
activated: ->
# Notification sent by the simulation that the given object was created.
created: (obj) ->
# Notification sent by the simulation that the given object was destroyed.
destroyed: (obj) ->
# Notification sent by the simulation that the given map cell has changed.
mapChanged: (cell, oldType, hadMine) ->
# All updates are processed by the active context.
activeContext = null
# Call +cb+ within the networking context +context+. This usually wraps calls to things that
# alter the simulation.
inContext = (ctx, cb) ->
activeContext = ctx
ctx.activated()
retval = cb()
activeContext = null
# Pass-through the return value of the callback.
retval
# Object types should register themselves, so they may be identified in messages.
types = {}
registerType = (character, type) ->
code = character.charCodeAt(0)
# Store the character on the prototype, so we can retrieve it from an object reference.
type.prototype._net_identifier = code
# Set up the alternative constructor.
type.fromNetwork = type.prototype.constructFromNetwork
type.fromNetwork.prototype = type.prototype
# Build a dictionary, so we can map an incoming message to the type.
types[code] = type
getTypeFromCode = (code) -> types[code]
# Exports.
exports.inContext = inContext
exports.registerType = registerType
exports.getTypeFromCode = getTypeFromCode
# Delegate the functions used by the simulation to the active context.
exports.created = (obj) -> activeContext?.created(obj)
exports.destroyed = (obj) -> activeContext?.destroyed(obj)
exports.mapChanged = (cell, oldType, hadMine) -> activeContext?.mapChanged(cell, oldType, hadMine)
# These are the server message identifiers both sides need to know about.
# The server sends binary data (encoded as base64). So we need to compare character codes.
exports.WELCOME_MESSAGE = 'W'.charCodeAt(0)
exports.CREATE_MESSAGE = 'C'.charCodeAt(0)
exports.DESTROY_MESSAGE = 'D'.charCodeAt(0)
exports.MAPCHANGE_MESSAGE = 'M'.charCodeAt(0)
exports.UPDATE_MESSAGE = 'U'.charCodeAt(0)
# And these are the client's messages. The client just sends one-character ASCII messages.
exports.START_TURNING_CCW = 'L'; exports.STOP_TURNING_CCW = 'l'
exports.START_TURNING_CW = 'R'; exports.STOP_TURNING_CW = 'r'
exports.START_ACCELERATING = 'A'; exports.STOP_ACCELERATING = 'a'
exports.START_BRAKING = 'B'; exports.STOP_BRAKING = 'b'
exports.START_SHOOTING = 'S'; exports.STOP_SHOOTING = 's'