Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

#!/usr/bin/env python3 

 

import contextlib 

 

from .errors import * 

from .tokens import Token, read_only, require_token, require_active_token 

 

class World(Token): 

 

def __init__(self): 

super().__init__() 

self._id = 0 

self._tokens = {} 

self._actors = [] 

self._is_locked = True 

self._has_game_ended = False 

with self._unlock_temporarily(): 

self._add_token(self) 

 

def __repr__(self): 

return '{}()'.format(self.__class__.__name__) 

 

def __iter__(self): 

# Make a copy of self._tokens.values() because it's possible for tokens  

# to be added or removed from the world while the world is being  

# iterated through. Concretely, this can happen when a token extension  

# sends a message to add or remove a token during on_update_game(). 

return (x for x in list(self._tokens.values()) if x is not self) 

 

def __len__(self): 

return len(self._tokens) 

 

def __contains__(self, token_or_id): 

id = token_or_id.id if isinstance(token_or_id, Token) else token_or_id 

return id in self._tokens 

 

def __getstate__(self): 

raise ApiUsageError("""\ 

can't pickle the world. 

 

The world should never have to be pickled and sent over the network, because  

each machine starts with its own world and is kept in sync by the messaging  

system. But unless you are explicitly trying to pickle the world on your own,  

this error is more likely to be the symptom of a major bug in the messaging  

system that is preventing it from correctly deciding which tokens need to be  

pickled.""") 

 

def __setstate__(self, state): 

raise AssertionError("""\ 

World.__getstate__ should've refused to pickle the world.""") 

 

@read_only 

def get_token(self, id): 

""" 

Return the token with the given id. If no token with the given id is  

registered to the world, an IndexError is thrown. 

""" 

return self._tokens[id] 

 

@read_only 

def get_last_id(self): 

""" 

Return the largest token id registered with the world. If no tokens  

have been added to the world, the id for the world itself (0) is  

returned. This means that the first "real" token id is 1. 

""" 

return max(self._tokens) 

 

@read_only 

def is_locked(self): 

""" 

Return whether or not the world is currently allowed to be modified. 

""" 

return self._is_locked 

 

def end_game(self): 

self._has_game_ended = True 

 

@read_only 

def has_game_ended(self): 

""" 

Return true if the game has ended. 

""" 

return self._has_game_ended 

 

def on_start_game(self): 

pass 

 

def on_update_game(self, dt): 

for token in self: 

token.on_update_game(dt) 

 

def on_finish_game(self): 

pass 

 

@contextlib.contextmanager 

def _unlock_temporarily(self): 

""" 

Allow tokens to modify the world for the duration of a with-block. 

 

It's important that tokens only modify the world at appropriate times,  

otherwise the changes they make may not be communicated across the  

network to other clients. To help catch and prevent these kinds of  

errors, the game engine keeps the world locked most of the time and  

only briefly unlocks it (using this method) when tokens are allowed to  

make changes. When the world is locked, token methods that aren't  

marked as being read-only can't be called. When the world is unlocked,  

any token method can be called. These checks can be disabled by  

running python with optimization enabled. 

 

You should never call this method manually from within your own game.  

This method is intended to be used by the game engine, which was  

carefully designed to allow the world to be modified only when safe.  

Calling this method yourself disables an important safety check. 

""" 

if not self._is_locked: 

yield 

else: 

try: 

self._is_locked = False 

yield 

finally: 

self._is_locked = True 

 

def _add_token(self, token): 

require_token(token) 

assert token.has_id, msg("""\ 

token {token} should've been assigned an id by  

Message._assign_token_ids() before World._add_token() was  

called.""") 

assert token not in self, msg("""\ 

Message._assign_token_ids() should've refused to process a  

token that was already in the world.""") 

 

info('adding token to world: {token}') 

 

# Add the token to the world. 

 

self._tokens[token.id] = token 

token._add_to_world(self, self._actors) 

 

return token 

 

def _remove_token(self, token): 

require_active_token(token) 

info('removing token from world: {token}') 

 

id = token.id 

token._remove_from_world() 

del self._tokens[id] 

 

def _get_nested_observers(self): 

return iter(self) 

 

def _set_actors(self, actors): 

""" 

Tell the world which actors are running on this machine. This  

information is used to create extensions for new tokens.  

""" 

self._actors = actors 

 

 

 

@debug_only 

def require_world(object): 

return require_instance(World(), object)