summaryrefslogtreecommitdiff
path: root/lib/supple/objects.lua
blob: 2f5da796c75352951aa812247ed5b701dcfa2428 (plain)
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
-- lib/supple/request.lua
--
-- Sandbox (for) Untrusted Procedure Partitioning (in) Lua Engine
--
-- Contextual object storage and identification.  Wrapping and unwrapping.
--
-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org>
--
-- For licence terms, see COPYING
--

local gc = collectgarbage

local capi = require 'supple.capi'
local track = require 'supple.track'

local my_objects_by_obj = {}
local my_objects_by_tag = {}
local my_objects_expn_by_tag = {}
local my_name = "UNSET"
local my_counter = 0
local their_objects_by_tag = setmetatable({}, { __mode="v" })
local their_objects_by_obj = setmetatable({}, { __mode="k" })
local their_objects_refcnt = {}
local proc_call = nil

local type = capi.rawtype

local function clean_down(call_other_end)
   track.enter("clean_down")
   -- And force a full GC
   gc "collect"
   gc "collect"
   gc "collect"
   -- Call the other end if needed
   if call_other_end then
      proc_call("supple:clean_down", "__call")
   end
   -- And forget all our local objects
   my_objects_by_obj = {}
   my_objects_by_tag = {}
   my_objects_expn_by_tag = {}
   track.leave("clean_down")
end

local function set_name(newname)
   my_name = newname
end

local function get_name()
   return my_name
end

local function set_proc_call(pc)
   proc_call = pc
end

local integral = {
   ["nil"] = true,
   boolean = true,
   string = true,
   number = true,
}

local function new_tag(special_tag)
   local retstr = special_tag
   if not retstr then
      if not my_name:match("%%d") then
	 retstr = ("%s:%d"):format(my_name, my_counter)
      else
	 retstr = my_name:format(my_counter)
      end
      my_counter = my_counter + 1
   end
   return retstr
end

local function give(obj, special_tag)
   -- If the object is integral, return it directly
   if integral[type(obj)] then
      return obj
   end
   -- If the passed object is one of their objects then "unwrap" it by
   -- returning their tag.
   track.enter("give", tostring(obj), tostring(special_tag))
   local tag = their_objects_by_obj[obj];
   if tag then
      track.leave("give", "theirs", tag)
      return { tag = tag }
   end
   -- If it's one of our objects which has already been wrapped, return our
   -- tag
   local tag = my_objects_by_obj[obj]
   if tag then
      track.leave("give", "ours", tag)
      return my_objects_expn_by_tag[tag]
   end
   -- otherwise wrap it freshly for us and return that.
   local tag = new_tag(special_tag)
   local expn = capi.explain(obj, tag)
   my_objects_by_obj[obj] = tag
   my_objects_by_tag[tag] = obj
   my_objects_expn_by_tag[tag] = expn
   track.leave("give", "ours, new", tag)
   return expn
end

local function receive(obj)
   -- If the object is integral, return it directly
   if integral[type(obj)] then
      return obj
   end
   -- It's not integral, so it must be a tagged object
   assert(type(obj) == "table")
   assert(type(obj.tag) == "string")
   local tag = obj.tag
   track.enter("receive", tag)
   -- First up, is it one of our objects?
   local ret = my_objects_by_tag[tag]
   if ret then
      track.leave("receive", "ours", tostring(ret))
      return ret
   end
   -- Now is it a known one of their objects?
   ret = their_objects_by_tag[tag]
   if ret then
      track.leave("receive", "theirs", tostring(ret))
      return ret
   end
   -- Okay, prepare a proxy?
   assert(type(obj.type) == "string", "Type string expected, got " .. type(obj.type) .. " preparing proxy for " .. obj.tag)
   local proxy, mt = capi.new_proxy(obj.type)
   assert(capi.type(proxy) == obj.type)
   their_objects_by_tag[tag] = proxy
   their_objects_by_obj[proxy] = tag
   their_objects_refcnt[tag] = (their_objects_refcnt[tag] or 0) + 1
   -- Fill out the metatable
   obj.methods = obj.methods or {}
   if obj.type == "function" then
      obj.methods[#obj.methods+1] = "__call"
   end
   if obj.type == "table" then
      obj.methods[#obj.methods+1] = "__len"
      obj.methods[#obj.methods+1] = "__index"
      obj.methods[#obj.methods+1] = "__newindex"
      obj.methods[#obj.methods+1] = "__next"
   end
   for _, name in ipairs(obj.methods or {}) do
      local function meta_func(mobj, ...)
	 track.enter("meta_func", tostring(mobj), tag, name)
	 local ret = {proc_call(tag, name, ...)}
	 track.leave("meta_func", tostring(mobj), tag, name)
	 return unpack(ret)
      end
      mt[name] = meta_func
   end
   function mt:__gc()
      their_objects_refcnt[tag] = their_objects_refcnt[tag] - 1
      if their_objects_refcnt[tag] == 0 then
	 their_objects_refcnt[tag] = nil
	 proc_call(tag, "__gc")
      end
   end
   -- And return the proxy object
   track.leave("receive", "theirs, new", tostring(proxy))
   return proxy
end

local function forget_mine(tag)
   track.enter("forget_mine", tag)
   local obj = my_objects_by_tag[tag]
   assert(obj, "Trying to forget nil object: " .. tag)
   my_objects_by_tag[tag] = nil
   my_objects_by_obj[obj] = nil
   my_objects_expn_by_tag[tag] = nil
   track.leave("forget_mine", tag)
end

local function find_tag(obj)
   if my_objects_by_obj[obj] then
      return "my", my_objects_by_obj[obj]
   end
   if their_objects_by_obj[obj] then
      return "their", their_objects_by_obj[obj]
   end
end

local function get_object_anchor()
   local ret = {}
   for obj in pairs(their_objects_by_obj) do
      ret[obj] = true
   end
   for obj in pairs(my_objects_by_obj) do
      ret[obj] = true
   end
   return ret
end

return {
   set_name = set_name,
   get_name = get_name,
   set_proc_call = set_proc_call,
   give = give,
   receive = receive,
   clean_down = clean_down,
   forget_mine = forget_mine,
   find_tag = find_tag,
   get_object_anchor = get_object_anchor,
}