Class: Live::Page

Inherits:
Object
  • Object
show all
Defined in:
lib/live/page.rb

Overview

Represents a connected client page with bound dynamic content areas.

Instance Method Summary collapse

Constructor Details

#initialize(resolver) ⇒ Page

Returns a new instance of Page.



21
22
23
24
25
26
27
28
# File 'lib/live/page.rb', line 21

def initialize(resolver)
	@resolver = resolver
	
	@elements = {}
	@attached = {}
	
	@updates = Async::Queue.new
end

Instance Method Details

#attach(element) ⇒ Object

Attach a pre-existing element to the page, so that it may later be bound to this exact instance. You must later detach the element when it is no longer needed.



40
41
42
# File 'lib/live/page.rb', line 40

def attach(element)
	@attached[element.id] = element
end

#bind(element) ⇒ Object

Bind a client-side element to a server side element.



32
33
34
35
36
# File 'lib/live/page.rb', line 32

def bind(element)
	@elements[element.id] = element
	
	element.bind(self)
end

#closeObject



74
75
76
77
78
79
80
81
82
# File 'lib/live/page.rb', line 74

def close
	@elements.each do |id, element|
		begin
			element.close
		rescue => error
			Console::Event::Failure.for(error).emit(self)
		end
	end
end

#detach(element) ⇒ Object



44
45
46
47
48
# File 'lib/live/page.rb', line 44

def detach(element)
	if @attached.delete(element.id)
		element.close
	end
end

#enqueue(update) ⇒ Object



84
85
86
# File 'lib/live/page.rb', line 84

def enqueue(update)
	@updates.enqueue(::Protocol::WebSocket::TextMessage.generate(update))
end

#handle(id, event) ⇒ Object

Handle an event from the client. If the element could not be found, it is silently ignored.



64
65
66
67
68
69
70
71
72
# File 'lib/live/page.rb', line 64

def handle(id, event)
	if element = @elements[id]
		return element.handle(event)
	else
		Console.warn(self, "Could not handle event:", id:, event:)
	end
	
	return nil
end

#process_message(message) ⇒ Object

Process a single incoming message from the network.



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
# File 'lib/live/page.rb', line 89

def process_message(message)
	case message[0]
	when "bind"
		# Bind a client-side element to a server-side element.
		if element = self.resolve(message[1], message[2])
			self.bind(element)
		else
			Console.warn(self, "Could not resolve element:", message)
			self.enqueue(["error", message[1], "Could not resolve element!"])
		end
	when "unbind"
		# Unbind a client-side element from a server-side element.
		if element = @elements.delete(message[1])
			element.close unless @attached.key?(message[1])
		else
			Console.warn(self, "Could not unbind element:", message)
			self.enqueue(["error", message[1], "Could not unbind element!"])
		end
	when "event"
		# Handle an event from the client.
		self.handle(message[1], message[2])
	else
		Console.warn(self, "Unhandled message:", message)
	end
end

#resolve(id, data = {}) ⇒ Object

Resolve a client-side element to a server side instance.



53
54
55
56
57
# File 'lib/live/page.rb', line 53

def resolve(id, data = {})
	@attached.fetch(id) do
		@resolver.call(id, data)
	end
end

#run(connection, keep_alive: 10) ⇒ Object

Run the event handling loop with the given websocket connection.



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
# File 'lib/live/page.rb', line 117

def run(connection, keep_alive: 10)
	Sync do |task|
		last_update = Async::Clock.now
		
		queue_task = task.async do
			while update = @updates.dequeue
				update.send(connection)
				
				# Flush the output if there are no more updates:
				if @updates.empty?
					connection.flush
				end
			end
		end
		
		keep_alive_task = task.async do
			while true
				sleep(keep_alive)
				
				duration = Async::Clock.now - last_update
				
				# We synchronize all writes to the update queue:
				if duration > keep_alive
					@updates.enqueue(::Protocol::WebSocket::PingMessage.new)
				end
			end
		end
		
		while message = connection.read
			last_update = Async::Clock.now
			process_message(message.parse)
		end
	ensure
		keep_alive_task&.stop
		
		self.close
		
		queue_task&.stop
	end
end