mirror of
https://github.com/holub/mame
synced 2025-04-23 17:00:53 +03:00
Merge pull request #148 from lucab/lucab/mame-lua/misc
luaengine: screen drawing fixes and initial docs
This commit is contained in:
commit
1e09899b01
154
docs/luaengine.md
Normal file
154
docs/luaengine.md
Normal file
@ -0,0 +1,154 @@
|
||||
# Scripting MAME via LUA
|
||||
|
||||
## Introduction
|
||||
|
||||
It is now possible to externally drive MAME via LUA scripts.
|
||||
This feature initially appeared in version 0.148, when a minimal `luaengine`
|
||||
was implemented. Nowadays, the LUA interface is rich enough
|
||||
to let you inspect and manipulate devices state, access CPU
|
||||
registers, read and write memory, and draw a custom HUD on screen.
|
||||
|
||||
Internally, MAME makes extensive use of `luabridge` to implement
|
||||
this feature: the idea is to transparently expose as many of
|
||||
the useful internals as possible.
|
||||
|
||||
Finally, a warning: LUA API is not yet declared stable and may
|
||||
suddenly change without prior notice.
|
||||
However, we expose methods to let you know at runtime which API
|
||||
version you are running against, and you can introspect most of the
|
||||
objects at runtime.
|
||||
|
||||
## Features
|
||||
|
||||
The API is not yet complete, but this is a partial list of capabilities
|
||||
currently available to LUA scripts:
|
||||
|
||||
* machine metadata (app version, current rom, rom details)
|
||||
* machine control (starting, pausing, resetting, stopping)
|
||||
* machine hooks (on frame painting and on user events)
|
||||
* devices introspection (device tree listing, memory and register enumeration)
|
||||
* screens introspection (screens listing, screen details, frames counting)
|
||||
* screen HUD drawing (text, lines, boxes on multiple screens)
|
||||
* memory read/write (8/16/32/64 bits, signed and unsigned)
|
||||
* registers and states control (states enumeration, get and set)
|
||||
|
||||
## Usage
|
||||
|
||||
MAME supports external scripting via LUA (>= 5.3) scripts, either
|
||||
written on the interactive console or loaded as a file.
|
||||
To reach the console, just run MAME with `-console`; you will be
|
||||
greeted by a naked `>` prompt where you can input your script.
|
||||
|
||||
To load a whole script at once, store it in a plaintext file and
|
||||
pass it via the `-autoboot_script`. Please note that script
|
||||
loading may be delayed (few seconds by default), but you can
|
||||
override the default with the `-autoboot_delay` argument.
|
||||
|
||||
To control the execution of your code, you can use a loop-based or
|
||||
an event-based approach. The former is not encouraged as it is
|
||||
resource-intensive and makes control flow unnecessarily complex.
|
||||
Instead, we suggest to register custom hooks to be invoked on specific
|
||||
events (eg. at each frame rendering).
|
||||
|
||||
## Walktrough
|
||||
|
||||
Let's first run MAME in a terminal to reach the LUA console:
|
||||
```
|
||||
$ mame -console YOUR_ROM
|
||||
M.A.M.E. v0.158 (Feb 5 2015) - Multiple Arcade Machine Emulator
|
||||
Copyright Nicola Salmoria and the MAME team
|
||||
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
|
||||
|
||||
>
|
||||
```
|
||||
|
||||
At this point, your game is probably running in demo mode, let's pause it:
|
||||
```
|
||||
> emu.pause()
|
||||
>
|
||||
```
|
||||
Even without textual feedback on the console, you'll notice the game is now paused.
|
||||
In general, commands are quiet and only print back error messages.
|
||||
|
||||
You can check at runtime which version of MAME you are running, with:
|
||||
```
|
||||
> print(emu.app_name() .. " " .. emu.app_version())
|
||||
mame 0.158
|
||||
```
|
||||
|
||||
We now start exploring screen related methods. First, let's enumerate available screens:
|
||||
```
|
||||
> for i,v in pairs(manager:machine().screens) do print(i) end
|
||||
:screen
|
||||
```
|
||||
|
||||
`manager:machine()` is the root object of your currently running machine:
|
||||
we will be using this often. `screens` is a table with all available screens;
|
||||
most machines only have one main screen.
|
||||
In our case, the main and only screen is tagged as `:screen`, and we can further
|
||||
inspect it:
|
||||
```
|
||||
> -- let's define a shorthand for the main screen
|
||||
> s = manager:machine().screens[":screen"]
|
||||
> print(s:width() .. "x" .. s:height())
|
||||
320x224
|
||||
```
|
||||
|
||||
We have several methods to draw on the screen a HUD composed of lines, boxes and text:
|
||||
```
|
||||
> -- we define a HUD-drawing function, and then call it
|
||||
> function draw_hud()
|
||||
>> s:draw_text(40, 40, "foo"); -- (x0, y0, msg)
|
||||
>> s:draw_box(20, 20, 80, 80, 0, 0xff00ffff); -- (x0, y0, x1, y1, fill-color, line-color)
|
||||
>> s:draw_line(20, 20, 80, 80, 0xff00ffff); -- (x0, y0, x1, y1, line-color)
|
||||
>> end
|
||||
> draw_hud();
|
||||
```
|
||||
|
||||
This will draw some useless art on the screen. However, when unpausing the game, your HUD
|
||||
needs to be refreshed otherwise it will just disappear. In order to do this, you have to register
|
||||
your hook to be called on every frame repaint:
|
||||
```
|
||||
> emu.sethook(draw_hud, "frame")
|
||||
```
|
||||
|
||||
Similarly to screens, you can inspect all the devices attached to a
|
||||
machine:
|
||||
```
|
||||
> for k,v in pairs(manager:machine().devices) do print(k) end
|
||||
:audiocpu
|
||||
:maincpu
|
||||
:saveram
|
||||
:screen
|
||||
:palette
|
||||
[...]
|
||||
```
|
||||
|
||||
On some of them, you can also inspect and manipulate memory and state:
|
||||
```
|
||||
> cpu = manager:machine().devices[":maincpu"]
|
||||
> -- enumerate, read and write state registers
|
||||
> for k,v in pairs(cpu.state) do print(k) end
|
||||
D5
|
||||
SP
|
||||
A4
|
||||
A3
|
||||
D0
|
||||
PC
|
||||
[...]
|
||||
> print(cpu.state["D0"].value)
|
||||
303
|
||||
> cpu.state["D0"].value = 255
|
||||
> print(cpu.state["D0"].value)
|
||||
255
|
||||
```
|
||||
|
||||
```
|
||||
> -- inspect memory
|
||||
> for k,v in pairs(cpu.spaces) do print(k) end
|
||||
program
|
||||
> mem = cpu.spaces["program"]
|
||||
> print(mem:read_i8(0xC000))
|
||||
41
|
||||
```
|
||||
|
@ -639,11 +639,13 @@ int lua_engine::lua_screen::l_draw_box(lua_State *L)
|
||||
luaL_argcheck(L, lua_isnumber(L, 7), 7, "outline color (integer) expected");
|
||||
|
||||
// retrieve all parameters
|
||||
int sc_width = sc->visible_area().width();
|
||||
int sc_height = sc->visible_area().height();
|
||||
float x1, y1, x2, y2;
|
||||
x1 = MIN(lua_tounsigned(L, 2) / static_cast<float>(sc->visible_area().width()) , 1.0f);
|
||||
y1 = MIN(lua_tounsigned(L, 3) / static_cast<float>(sc->visible_area().height()), 1.0f);
|
||||
x2 = MIN(lua_tounsigned(L, 4) / static_cast<float>(sc->visible_area().width()) , 1.0f);
|
||||
y2 = MIN(lua_tounsigned(L, 5) / static_cast<float>(sc->visible_area().height()), 1.0f);
|
||||
x1 = MIN(MAX(0, lua_tointeger(L, 2)), sc_width-1) / static_cast<float>(sc_width);
|
||||
y1 = MIN(MAX(0, lua_tointeger(L, 3)), sc_height-1) / static_cast<float>(sc_height);
|
||||
x2 = MIN(MAX(0, lua_tointeger(L, 4)), sc_width-1) / static_cast<float>(sc_width);
|
||||
y2 = MIN(MAX(0, lua_tointeger(L, 5)), sc_height-1) / static_cast<float>(sc_height);
|
||||
UINT32 bgcolor = lua_tounsigned(L, 6);
|
||||
UINT32 fgcolor = lua_tounsigned(L, 7);
|
||||
|
||||
@ -675,11 +677,13 @@ int lua_engine::lua_screen::l_draw_line(lua_State *L)
|
||||
luaL_argcheck(L, lua_isnumber(L, 6), 6, "color (integer) expected");
|
||||
|
||||
// retrieve all parameters
|
||||
int sc_width = sc->visible_area().width();
|
||||
int sc_height = sc->visible_area().height();
|
||||
float x1, y1, x2, y2;
|
||||
x1 = MIN(lua_tounsigned(L, 2) / static_cast<float>(sc->visible_area().width()) , 1.0f);
|
||||
y1 = MIN(lua_tounsigned(L, 3) / static_cast<float>(sc->visible_area().height()), 1.0f);
|
||||
x2 = MIN(lua_tounsigned(L, 4) / static_cast<float>(sc->visible_area().width()) , 1.0f);
|
||||
y2 = MIN(lua_tounsigned(L, 5) / static_cast<float>(sc->visible_area().height()), 1.0f);
|
||||
x1 = MIN(MAX(0, lua_tointeger(L, 2)), sc_width-1) / static_cast<float>(sc_width);
|
||||
y1 = MIN(MAX(0, lua_tointeger(L, 3)), sc_height-1) / static_cast<float>(sc_height);
|
||||
x2 = MIN(MAX(0, lua_tointeger(L, 4)), sc_width-1) / static_cast<float>(sc_width);
|
||||
y2 = MIN(MAX(0, lua_tointeger(L, 5)), sc_height-1) / static_cast<float>(sc_height);
|
||||
UINT32 color = lua_tounsigned(L, 6);
|
||||
|
||||
// draw the line
|
||||
@ -705,8 +709,10 @@ int lua_engine::lua_screen::l_draw_text(lua_State *L)
|
||||
luaL_argcheck(L, lua_isstring(L, 4), 4, "message (string) expected");
|
||||
|
||||
// retrieve all parameters
|
||||
float x = MIN(lua_tounsigned(L, 2) / static_cast<float>(sc->visible_area().width()) , 1.0f);
|
||||
float y = MIN(lua_tounsigned(L, 3) / static_cast<float>(sc->visible_area().height()), 1.0f);
|
||||
int sc_width = sc->visible_area().width();
|
||||
int sc_height = sc->visible_area().height();
|
||||
float x = MIN(MAX(0, lua_tointeger(L, 2)), sc_width-1) / static_cast<float>(sc_width);
|
||||
float y = MIN(MAX(0, lua_tointeger(L, 3)), sc_height-1) / static_cast<float>(sc_height);
|
||||
const char *msg = luaL_checkstring(L,4);
|
||||
// TODO: add optional parameters (colors, etc.)
|
||||
|
||||
|
@ -2483,26 +2483,41 @@ render_target *render_manager::target_by_index(int index) const
|
||||
// fonts
|
||||
//-------------------------------------------------
|
||||
|
||||
float render_manager::ui_aspect()
|
||||
float render_manager::ui_aspect(render_container *rc)
|
||||
{
|
||||
int orient = orientation_add(m_ui_target->orientation(), m_ui_container->orientation());
|
||||
int orient = 0;
|
||||
float aspect = 1.0f;
|
||||
|
||||
// based on the orientation of the target, compute height/width or width/height
|
||||
float aspect;
|
||||
if (!(orient & ORIENTATION_SWAP_XY))
|
||||
aspect = (float)m_ui_target->height() / (float)m_ui_target->width();
|
||||
else
|
||||
aspect = (float)m_ui_target->width() / (float)m_ui_target->height();
|
||||
if (rc == m_ui_container || rc == NULL) {
|
||||
// ui container, aggregated multi-screen target
|
||||
|
||||
// if we have a valid pixel aspect, apply that and return
|
||||
if (m_ui_target->pixel_aspect() != 0.0f)
|
||||
return aspect / m_ui_target->pixel_aspect();
|
||||
orient = orientation_add(m_ui_target->orientation(), m_ui_container->orientation());
|
||||
// based on the orientation of the target, compute height/width or width/height
|
||||
if (!(orient & ORIENTATION_SWAP_XY))
|
||||
aspect = (float)m_ui_target->height() / (float)m_ui_target->width();
|
||||
else
|
||||
aspect = (float)m_ui_target->width() / (float)m_ui_target->height();
|
||||
|
||||
// if not, clamp for extreme proportions
|
||||
// if we have a valid pixel aspect, apply that and return
|
||||
if (m_ui_target->pixel_aspect() != 0.0f)
|
||||
return (aspect / m_ui_target->pixel_aspect());
|
||||
} else {
|
||||
// single screen container
|
||||
|
||||
orient = rc->orientation();
|
||||
// based on the orientation of the target, compute height/width or width/height
|
||||
if (!(orient & ORIENTATION_SWAP_XY))
|
||||
aspect = (float)rc->screen()->visible_area().height() / (float)rc->screen()->visible_area().width();
|
||||
else
|
||||
aspect = (float)rc->screen()->visible_area().width() / (float)rc->screen()->visible_area().height();
|
||||
}
|
||||
|
||||
// clamp for extreme proportions
|
||||
if (aspect < 0.66f)
|
||||
aspect = 0.66f;
|
||||
if (aspect > 1.5f)
|
||||
aspect = 1.5f;
|
||||
|
||||
return aspect;
|
||||
}
|
||||
|
||||
|
@ -744,7 +744,7 @@ public:
|
||||
// UI targets
|
||||
render_target &ui_target() const { assert(m_ui_target != NULL); return *m_ui_target; }
|
||||
void set_ui_target(render_target &target) { m_ui_target = ⌖ }
|
||||
float ui_aspect();
|
||||
float ui_aspect(render_container *rc = NULL);
|
||||
|
||||
// UI containers
|
||||
render_container &ui_container() const { assert(m_ui_container != NULL); return *m_ui_container; }
|
||||
|
@ -456,7 +456,7 @@ void ui_manager::update_and_render(render_container *container)
|
||||
{
|
||||
float mouse_y=-1,mouse_x=-1;
|
||||
if (mouse_target->map_point_container(mouse_target_x, mouse_target_y, *container, mouse_x, mouse_y)) {
|
||||
container->add_quad(mouse_x,mouse_y,mouse_x + 0.05*container->manager().ui_aspect(),mouse_y + 0.05,UI_TEXT_COLOR,m_mouse_arrow_texture,PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
|
||||
container->add_quad(mouse_x,mouse_y,mouse_x + 0.05*container->manager().ui_aspect(container),mouse_y + 0.05,UI_TEXT_COLOR,m_mouse_arrow_texture,PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -600,7 +600,7 @@ void ui_manager::draw_text_full(render_container *container, const char *origs,
|
||||
const char *linestart;
|
||||
float cury = y;
|
||||
float maxwidth = 0;
|
||||
float aspect = machine().render().ui_aspect();
|
||||
float aspect = machine().render().ui_aspect(container);
|
||||
|
||||
// if we don't want wrapping, guarantee a huge wrapwidth
|
||||
if (wrap == WRAP_NEVER)
|
||||
|
Loading…
Reference in New Issue
Block a user