lua inheritance on existing object - class

I am writing a new constructor and I have something like this:
function Map:new(path, world, debug)
local map = sti(path, { "box2d" })
return map
end
function Map:update(dt)
print('call this')
end
sti is some thirdparty library that constructs a class object.
What I am trying to do is make it so when I call:
map:update(dt)
it calls the functions I have declared. If not found, it calls the actual function that sti sets up on the object.
I've tried stuffing around with metatables but can't seem to get my functions to take priority over the third party library supplied functions....

Reading the source code for what I believe is the library you're using (Simple-Tiled-Implementation), I figured out it actually overrides your metatable with another one:
local function new(map, plugins, ox, oy)
local dir = ""
if type(map) == "table" then
map = setmetatable(map, Map) -- Here
else
-- Check for valid map type
local ext = map:sub(-4, -1)
assert(ext == ".lua", string.format(
"Invalid file type: %s. File must be of type: lua.",
ext
))
-- Get directory of map
dir = map:reverse():find("[/\\]") or ""
if dir ~= "" then
dir = map:sub(1, 1 + (#map - dir))
end
-- Load map
map = setmetatable(assert(love.filesystem.load(map))(), Map) -- Or here
end
map:init(dir, plugins, ox, oy)
return map
end
The function above is defined here
You'll need to pass a table argument as map instead of the path, there you can define update(), which will take precedence over the metatable provided by STI.
I believe you can copy the procedure STI does to load your map and provide it with a table containing the function you wish to define inside:
-- Check for valid map type
local ext = map:sub(-4, -1)
assert(ext == ".lua", string.format(
"Invalid file type: %s. File must be of type: lua.",
ext
))
-- Get directory of map
dir = map:reverse():find("[/\\]") or ""
if dir ~= "" then
dir = map:sub(1, 1 + (#map - dir))
end
-- Load map
local map = assert(love.filesystem.load(map))()
function map:update()
-- Do things
end
sti(map, { "box2d" })

unfortunately sti declares 'local dir' at the top of the function so copying the code did not work.
I found a solution how ever I have made myself some way to easily set a class as a proxy in lua:
-- forward a function call from oldSelf:fn(...) to newSelf:fn(...)
function utils.forwardFunc(fn, newSelf)
return function(oldSelf, ...)
local function __NULL__() end
return (fn or __NULL__)(newSelf, ...)
end
end
-- make a function fn(...) call newSelf:fn(...)
function utils.func(fn, newSelf)
return function(...)
local function __NULL__() end
return (fn or __NULL__)(newSelf, ...)
end
end
-- forward any undefined functions called on 'from' to 'to'
-- if 'to' is a function, it acts as a dynamic proxy, incase you are changing what class you are proxying to
-- on the fly. For example, a state machine proxies to the current state
function utils.proxyClass(from, to)
local mt = getmetatable(from)
setmetatable(from, {__index = function(_, func)
if mt and mt[func] then
return mt[func]
end
local forwardTo = to
if type(to) == 'function' then
forwardTo = to(from)
end
if type(forwardTo[func]) == 'function' then
return utils.forwardFunc(forwardTo[func], forwardTo)
else
return forwardTo[func]
end
end})
end

Related

Issue on attach_mappings with Telescope find_files picker

I try to create a function with the Neovim Telescope plugin and find_files builtin picker to list my configuration files (in ~/.config/nvim/lua directory). But I have an issue to use a specific mapping (here defined with CTRL-e) after selecting an entry via Telescope.
My lua/reload.lua file :
local M = {}
M.reload = function()
local opts = {
prompt_title = 'Configuration files',
cwd = '~/.config/nvim/lua',
attach_mappings = function(_, map)
local action_state = require('telescope.actions.state')
-- Adds a new map to ctrl+e.
map('i', '<C-e>', function(_)
local entry = action_state.get_selected_entry()
local name = get_module_name(entry.value)
print('Name = ' .. name)
return true
end,
}
-- call the builtin method to list files
require('telescope.builtin').find_files(opts)
end
return M
When I call reload method require('reload').reload(), the Telescope find_files picker is open correctly, I can select a file in the list but my CTRL-e mapping does not work => function to print selected filename not called.
Have some clue to help me ?
You will need to press ctr+e before selecting the item in the list.

Error in save as type using uiputfile - Matlab

In app designer I have two buttons one to declare the working folder:
function setSaveLocationButtonPushed(app, event)
app.path = uigetdir()
end
The other one to save an image
function saveButtonPushed(app, event)
pathSave = app.path;
[file, pathSave] = uiputfile([pathSave,'*.jpg']);
…
end
Why do I get in the save as type also the app.path? (as shown in image)
Your code [pathSave,'*.jpg'] concatenates the path and the filter and then passes the result as the only argument to the uiputfile function. This argument tells the function what file filter to use.
Instead of storing the chosen directory, make it change the current directory. The file selection UI always opens in the current directory.
function setSaveLocationButtonPushed(app, event)
p = uigetdir;
cd(p)
end
function saveButtonPushed(app, event)
[file, pathSave] = uiputfile('*.jpg');
…
end
If you don’t want to change the current directory for the whole application, you can change it just before calling the uiputfile function, and change it back afterward:
function saveButtonPushed(app, event)
p = cd(app.path);
[file, pathSave] = uiputfile('*.jpg');
cd(p);
…
end

Roblox- how to store large arrays in roblox datastores

i am trying to make a game where players create their own buildings and can then save them for other players to see and play on. However, roblox doesn't let me store all the data needed for the whole creation(there are several properties for each brick)
All i get is this error code:
104: Cannot store Array in DataStore
any help would be greatly appreciated!
I'm not sure if this is the best method, but it's my attempt. Below is an example of a table, you can use tables to store several values. I think you can use HttpService's JSONEncode function to convert tables into strings (which hopefully can be saved more efficiently)
JSONEncode (putting brick's data into a string, which you can save into the DataStore
local HttpService = game:GetService("HttpService")
-- this is an example of what we'll convert into a json string
local exampleBrick = {
["Size"] = Vector3.new(3,3,3),
["Position"] = Vector3.new(0,1.5,0),
["BrickColor"] = BrickColor.new("White")
["Material"] = "Concrete"
}
local brickJSON = HttpService:JSONEncode(exampleBrick)
print(brickJSON)
-- when printed, you'll get something like
-- { "Size": Vector3.new(3,3,3), "Position": Vector3.new(0,1.5,0), "BrickColor": BrickColor.new("White"), "Material": "Concrete"}
-- if you want to refer to this string in a script, surround it with two square brackets ([[) e.g. [[{"Size": Vector3.new(3,3,3)... }]]
JSONDecode (reading the string and converting it back into a brick)
local HttpService = game:GetService("HttpService")
local brickJSON = [[ {"Size": Vector3.new(3,3,3), "Position": Vector3.new(0,1.5,0), "BrickColor": BrickColor.new("White"), "Material": "Concrete"} ]]
function createBrick(tab)
local brick = Instance.new("Part")
brick.Parent = <insert parent here>
brick.Size = tab[1]
brick.Position= tab[2]
brick.BrickColor= tab[3]
brick.Material= tab[4]
end
local brickData = HttpService:JSONDecode(brickJSON)
createBrick(brickData) --this line actually spawns the brick
The function can also be wrapped in a pcall if you want to account for any possible datastore errors.
Encoding a whole model into a string
Say your player's 'building' is a model, you can use the above encode script to convert all parts inside a model into a json string to save.
local HttpService = game:GetService("HttpService")
local StuffWeWantToSave = {}
function getPartData(part)
return( {part.Size,part.Position,part.BrickColor,part.Material} )
end
local model = workspace.Building --change this to what the model is
local modelTable = model:Descendants()
for i,v in pairs(modelTable) do
if v:IsA("Part") or v:IsA("WedgePart") then
table.insert(StuffWeWantToSave, HttpService:JSONEncode(getPartData(modelTable[v])))
end
end
Decoding a string into a whole model
This will probably occur when the server is loading a player's data.
local HttpService = game:GetService("HttpService")
local SavedStuff = game:GetService("DataStoreService"):GetDataStore("blabla") --I don't know how you save your data, so you'll need to adjust this and the rest of the scripts (as long as you've saved the string somewhere in the player's DataStore)
function createBrick(tab)
local brick = Instance.new("Part")
brick.Parent = <insert parent here>
brick.Size = tab[1]
brick.Position= tab[2]
brick.BrickColor= tab[3]
brick.Material= tab[4]
end
local model = Instance.new("Model") --if you already have 'bases' for the players to load their stuff in, remove this instance.new
model.Parent = workspace
for i,v in pairs(SavedStuff) do
if v[1] ~= nil then
CreateBrick(v)
end
end
FilteringEnabled
If your game uses filteringenabled, make sure that only the server handles saving and loading data!! (you probably already knew that) If you want the player to save by clicking a gui button, make the gui button fire a RemoteFunction that sends their base's data to the server to convert it to a string.
BTW I'm not that good at scripting so I've probably made a mistake somehwere.. good luck though
Crabway's answer is correct in that the HttpService's JSONEncode and JSONDecode methods are the way to go about tackling this problem. As it says on the developer reference page for the DataStoreService, Data is ... saved as a string in data stores, regardless of its initial type. (https://developer.roblox.com/articles/Datastore-Errors.) This explains the error you received, as you cannot simply push a table to the data store; instead, you must first encode a table's data into a string using JSONEncode.
While I agree with much of Crabway's answer, I believe the function createBrick would not behave as intended. Consider the following trivial example:
httpService = game:GetService("HttpService")
t = {
hello = 1,
goodbye = 2
}
s = httpService:JSONEncode(t)
print(s)
> {"goodbye":2,"hello":1}
u = httpService:JSONDecode(s)
for k, v in pairs(u) do print(k, v) end
> hello 1
> goodbye 2
As you can see, the table returned by JSONDecode, like the original, uses strings as keys rather than numeric indices. Therefore, createBrick should be written something like this:
function createBrick(t)
local brick = Instance.new("Part")
brick.Size = t.Size
brick.Position = t.Position
brick.BrickColor = t.BrickColor
brick.Material = t.Material
-- FIXME: set any other necessary properties.
-- NOTE: try to set parent last for optimization reasons.
brick.Parent = t.Parent
return brick
end
As for encoding a model, calling GetChildren would produce a table of the model's children, which you could then loop through and encode the properties of everything within. Note that in Crabway's answer, he only accounts for Parts and WedgeParts. You should account for all parts using object:IsA("BasePart") and also check for unions with object:IsA("UnionOperation"). The following is a very basic example in which I do not store the encoded data; rather, I am just trying to show how to check the necessary cases.
function encodeModel(model)
local children = model:GetChildren()
for _, child in ipairs(children) do
if ((child:IsA("BasePart")) or (child:IsA("UnionOperation"))) then
-- FIXME: encode child
else if (child:IsA("Model")) then
-- FIXME: using recursion, loop through the sub-model's children.
end
end
return
end
For userdata, such as Vector3s or BrickColors, you will probably want to convert those to strings when you go to encode them with JSONEncode.
-- Example: part with "Brick red" BrickColor.
color = tostring(part.BrickColor)
print(string.format("%q", color))
> "Bright red"
I suggest what #Crabway said, use HttpService.
local httpService = game:GetService("HttpService")
print(httpService:JSONEncode({a = "b", b = "c"}) -- {"a":"b","b":"c"}
But if you have any UserData values such as Vector3s, CFrames, Color3s, BrickColors and Enum items, then use this library by Defaultio. It's actually pretty nice.
local library = require(workspace:WaitForChild("JSONWithUserdata"))
library:Encode({Vector3.new(0, 0, 0)})
If you want a little documentation, then look at the first comment in the script:
-- Defaultio
--[[
This module adds support for encoding userdata values to JSON strings.
It also supports lists which skip indices, such as {[1] = "a", [2] = "b", [4] = "c"}
Userdata support is implemented by replacing userdata types with a new table, with keys _T and _V:
_T = userdata type enum (index in the supportedUserdataTypes list)
_V = a value or table representing the value
Follow the examples bellow to add suppport for additional userdata types.
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Usage example:
local myTable = {CFrame.new(), BrickColor.Random(), 4, "String", Enum.Material.CorrodedMetal}
local jsonModule = require(PATH_TO_MODULE)
local jsonString = jsonModule:Encode(myTable)
local decodedTable = jsonModule:Decode(jsonString)
--]]

ABAP: Event CONTEXT_MENU_SELECT of CL_SIMPLE_TREE_MODEL - can't fire it

I have an instance of CL_SIMPLE_TREE_MODEL, I managed to trigger and handle the CONTEXT_MENU_REQUEST event, and I build my context menu.
I added my functions the way I wanted.
Problem is, when I select one of the options from my context menu, nothing happens. In other words, the program flow doesn't go inside the handler for CONTEXT_MENU_SELECT.
I'm of course assuming this event is fired when I click on a function in the context menu.
I found official documentation, but only for the "default context menu" that you access with Shift+F10, that needs certain subroutines in the program in order to fire.
What I did:
I did define and implement a method that is a handler for that event.
I did set the handler for the event at the same place I set the handler for the CONTEXT_MENU_REQUEST event
I did NOT register the event with the SET_REGISTERED_EVENTS because there is NO ID defined in the class attributes for that event - such as there was for the CONTEXT_MENU_REQUEST event.
The code:
REPORT.
CLASS lcl_tree_handler DEFINITION.
PUBLIC SECTION.
METHODS:
pbo,
on_ctx_menu_request FOR EVENT node_context_menu_request OF cl_simple_tree_model
IMPORTING node_key menu sender, "TYPE TM_NODEKEY CL_CTMENU
on_ctx_menu_select FOR EVENT node_context_menu_select OF cl_simple_tree_model
IMPORTING node_key fcode. "TYPE TM_NODEKEY SY-UCOMM
DATA:
po_tree_model TYPE REF TO cl_simple_tree_model,
gt_tree TYPE TABLE OF treemsnodt,
control TYPE REF TO cl_gui_control.
ENDCLASS.
CLASS lcl_tree_handler IMPLEMENTATION.
METHOD pbo.
DATA: lt_events TYPE cntl_simple_events,
ls_event TYPE cntl_simple_event.
FIELD-SYMBOLS <gs_tree> TYPE treemsnodt.
CHECK po_tree_model IS NOT BOUND.
CREATE OBJECT po_tree_model
EXPORTING
node_selection_mode = po_tree_model->node_sel_mode_single.
APPEND INITIAL LINE TO gt_tree ASSIGNING <gs_tree>.
<gs_tree>-node_key = 'Node key 1'.
<gs_tree>-text = 'First node'.
<gs_tree>-isfolder = 'X'.
APPEND INITIAL LINE TO gt_tree ASSIGNING <gs_tree>.
<gs_tree>-node_key = 'Node key 2'.
<gs_tree>-relatkey = 'Node key 1'.
<gs_tree>-relatship = cl_tree_model=>relat_last_child.
<gs_tree>-text = 'First child'.
po_tree_model->add_nodes(
node_table = gt_tree ).
ls_event-eventid = cl_simple_tree_model=>eventid_node_context_menu_req.
ls_event-appl_event = 'X'. "tried with space too
APPEND ls_event TO lt_events.
CALL METHOD po_tree_model->set_registered_events
EXPORTING
events = lt_events.
SET HANDLER on_ctx_menu_request FOR po_tree_model.
SET HANDLER on_ctx_menu_select FOR po_tree_model.
po_tree_model->create_tree_control(
EXPORTING
parent = cl_gui_container=>screen0
IMPORTING
control = control ).
ENDMETHOD.
METHOD on_ctx_menu_request. "I initialize the context menu object here.
DATA: lt_chidren_keys TYPE treemnotab,
ls_child_key TYPE tm_nodekey,
lv_text TYPE gui_text.
CALL METHOD sender->node_get_children
EXPORTING
node_key = node_key
IMPORTING
node_key_table = lt_chidren_keys
EXCEPTIONS
OTHERS = 2.
LOOP AT lt_chidren_keys INTO ls_child_key.
lv_text = ls_child_key.
CALL METHOD menu->add_function
EXPORTING
fcode = 'ONE'
text = lv_text
ftype = 'B'.
ENDLOOP.
menu->add_separator( ).
CALL METHOD menu->add_function
EXPORTING
fcode = 'ALL'
text = 'All the work groups'
ftype = 'W'.
ENDMETHOD.
METHOD on_ctx_menu_select.
BREAK-POINT. "tried actual code here too.
ENDMETHOD.
ENDCLASS.
DATA: go_tree_handler TYPE REF TO lcl_tree_handler.
PARAMETERS dummy.
INITIALIZATION.
CREATE OBJECT go_tree_handler.
AT SELECTION-SCREEN OUTPUT.
go_tree_handler->pbo( ).
AT SELECTION-SCREEN ON EXIT-COMMAND.
go_tree_handler->control->free( ).
The tree is displayed, on right click the context menu appears.
But nothing fires when I chose a menu item. Am I missing something ?
The function types you specify (ftype = 'W' and 'B') are not supported (check the fixed values of the underlying domain CUA_FUNTYP). In that case, nothing happens.
The classic solution is to use ftype = ' ' (normal function):
LOOP AT lt_chidren_keys INTO ls_child_key.
lv_text = ls_child_key.
CALL METHOD menu->add_function
EXPORTING
fcode = 'ONE'
text = lv_text
ftype = ' '.
ENDLOOP.
menu->add_separator( ).
CALL METHOD menu->add_function
EXPORTING
fcode = 'ALL'
text = 'All the work groups'
ftype = ' '.
The possible ftype values are (source: domain CUA_FUNTYP):
' ' : Normal function
'H' : Help function (PROCESS ON HELP REQUEST)
'S' : System function (handled directly by DYNP)
'T' : Transaction call (LEAVE TO TRANSACTION)
'E' : Access modules for 'AT EXIT COMMAND' -> /E as prefix
'I' : Include menu (replaced at runtime - not supported)
'N' : 'AT EXIT COMMAND' Function, > DYNP > /N as Prefix
Remark: the code in the first version of the question was missing the registering of the second event handler SET HANDLER go_tree_handler->on_ctx_menu_select FOR po_tree_model. (now it's okay)

How to use functions from another class in main.lua?

I am trying to use functions of another class in my main.lua. I wrote some code according to my research but it's not working properly. Can you help me out? Thanks.
fish.lua code:
function class()
local cls = {}
cls.__index = cls
return setmetatable(cls, {__call = function (c, ...)
instance = setmetatable({}, cls)
if cls.__init then
cls.__init(instance, ...)
end
return instance
end})
end
Fish= class()
function Fish:listen(event)
if phase =="began" then
print("hi")
end
end
function Fish:__init(image)
self.image=display.newImage(image,30,30)
self.image: addEventListener("touch",self.listen)
end
main.lua code:
require "fish"
originalImage="fish.small.red.png"
differentImage="fish.small.blue.png"
local fishImage=Fish(originalImage)
It displays the image but does not work(prints "hi" ) when it is touched.
A couple of problems:
Change function Fish:listen(event) to function Fish.listen(event)
and if phase =="began" then should be if event.phase =="began" then