OXYGENHUB
◆ main openmsx / share / scripts / _tas_tools.tcl
21.0 KiB · Tcl · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
namespace eval tas {
2
3
### periodic refresh stuff
4
5
variable callback_list ""
6
variable callback_frame_id
7
variable callback_realtime_id
8
9
# TODO move to utils?
10
# Register the given callback to be executed
11
#  - (Once) when it is registered
12
#  - Every MSX frame
13
#  - Or at least 10 times per second (e.g. when MSX emulation is paused)
14
proc enable_periodic {callback} {
15
	variable callback_list
16
	variable callback_frame_id
17
	variable callback_realtime_id
18
19
	# on 1st callback activate the after-stuff
20
	if {[llength $callback_list] == 0} {
21
		set callback_frame_id    [after frame        [namespace code periodic_frame]]
22
		set callback_realtime_id [after realtime 0.1 [namespace code periodic_realtime]]
23
	}
24
25
	# add to list of registered callbacks
26
	lappend callback_list $callback
27
28
	# execute callback for the first time
29
	$callback
30
}
31
32
# Remove a previously registered callback. After this proc returns, the
33
# callback is guaranteed to not be called anymore. So it's (no longer)
34
# required to add a check in the callback proc.
35
proc disable_periodic {callback} {
36
	variable callback_list
37
	variable callback_frame_id
38
	variable callback_realtime_id
39
40
	# remove from registered callbacks
41
	set idx [lsearch -exact $callback_list $callback]
42
	if {$idx == -1} return
43
	set callback_list [lreplace $callback_list $idx $idx]
44
45
	# if the last callback was removed then deactivate after stuff
46
	if {[llength $callback_list] == 0} {
47
		after cancel $callback_frame_id
48
		after cancel $callback_realtime_id
49
	}
50
}
51
52
proc periodic_execute {} {
53
	variable callback_list
54
	foreach callback $callback_list {
55
		$callback
56
	}
57
}
58
proc periodic_frame {} {
59
	variable callback_frame_id
60
	variable callback_realtime_id
61
62
	periodic_execute
63
64
	# postpone 'after realtime' / reschedule 'after frame'
65
	after cancel $callback_realtime_id
66
	set callback_frame_id    [after frame        [namespace code periodic_frame]]
67
	set callback_realtime_id [after realtime 0.1 [namespace code periodic_realtime]]
68
}
69
proc periodic_realtime {} {
70
	variable callback_realtime_id
71
72
	periodic_execute
73
74
	# reschedule 'after realtime', no need to handle 'after frame'
75
	set callback_realtime_id [after realtime 0.1 [namespace code periodic_realtime]]
76
}
77
78
79
### frame counter ###
80
81
set_help_text toggle_frame_counter\
82
{Toggles display of a frame counter in the lower right corner.}
83
84
proc toggle_frame_counter {} {
85
	if {[osd exists framecount]} {
86
		disable_periodic framecount_update
87
		osd destroy framecount
88
		return ""
89
	}
90
91
	osd create rectangle framecount \
92
		-x 269 -y 227 -h 7 -w 42 -scaled true \
93
		-rgba "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80" \
94
		-borderrgba 0x00000040 -bordersize 0.5
95
	osd create text framecount.text -x 3 -y 1 -size 4 -rgba 0xffffffff
96
	enable_periodic framecount_update
97
	return ""
98
}
99
100
proc framecount_update {} {
101
	# Heuristic: A 'ex (sp),ix' Z80 instruction takes 25 cycles. If we're
102
	# not more than 25 Z80 cycles past the start of a frame, then indicate
103
	# we're at the start of the frame. (But it could as well be the 2nd or
104
	# 3rd (short) instruction in the frame.)
105
	set inside [expr {([machine_info VDP_cycle_in_frame] < (6 * 25)) ? "" : "+"}]
106
	osd configure framecount.text -text "Frame: [machine_info VDP_frame_count]$inside"
107
}
108
109
110
### frame advance/reverse and helper procs for TAS mode key bindings ###
111
112
proc get_start_of_frame_time {} {
113
	expr {[machine_info time] - [machine_info VDP_cycle_in_frame] / (6.0 * 3579545)}
114
}
115
116
set_help_text prev_frame \
117
{Rewind to the (start of) the previous frame. Useful
118
to bind to a key in combination with next_frame. You can also
119
go back N frames if you add N as the optional argument.}
120
proc prev_frame {{count 1}} {
121
	set t [expr {[get_start_of_frame_time] - $count * [vdp::get_frame_duration]}]
122
	if {$t < 0} {set t 0}
123
	reverse goto $t
124
}
125
126
set_help_text next_frame \
127
{Emulates until the (start of) the next frame, then pause emulation.
128
Useful to bind to a key and emulate frame by frame. You can also
129
go over the next N frames if you add N as the optional argument.}
130
proc next_frame {{count 1}} {
131
	set t [expr {[get_start_of_frame_time] + $count * [vdp::get_frame_duration]}]
132
	after time [expr {$t - [machine_info time]}] "set ::pause on"
133
	set ::pause off
134
	return ""
135
}
136
137
set_help_text start_of_frame \
138
{Rewind to the start of the current frame.
139
See also prev_frame and next_frame.}
140
proc start_of_frame {} {
141
	prev_frame 0
142
}
143
144
set_help_text advance_frame \
145
{Emulate forward for the duration of one frame. The relative
146
timing position within the frame remains unchanged. You can also
147
advance N frames if you add N as the optional argument.}
148
proc advance_frame {{count 1}} {
149
	after time [expr {$count * [vdp::get_frame_duration]}] "set ::pause on"
150
	set ::pause off
151
	return ""
152
}
153
154
set_help_text reverse_frame \
155
{Emulate backward for the duration of one frame. The relative
156
timing position within the frame remains unchanged. You can also
157
reverse N frames if you add N as the optional argument.}
158
proc reverse_frame {{count 1}} {
159
	set t [expr {[machine_info time] - $count * [vdp::get_frame_duration]}]
160
	if {$t < 0} {set t 0}
161
	reverse goto $t
162
}
163
164
proc load_replay {name} {
165
	reverse loadreplay -goto savetime $name
166
	return ""
167
}
168
169
170
### Very basic replay slot selector ###
171
172
user_setting create string current_replay_slot "Name of the current replay slot." slot0
173
174
proc list_slots {} {
175
	set slots [list]
176
	for {set i 0} {$i <= 9} {incr i} {
177
		lappend slots "slot$i"
178
	}
179
	return $slots
180
}
181
182
proc menu_create_slot_menu {} {
183
	set items [list_slots]
184
	set menu_def \
185
		{ execute tas::set_slot
186
			font-size 8
187
			border-size 2
188
			width 100
189
			xpos 100
190
			ypos 100
191
			header { text "Select Replay Slot"
192
					font-size 10
193
					post-spacing 6 }}
194
195
	return [osd_menu::prepare_menu_list $items 10 $menu_def]
196
}
197
198
proc set_slot {item} {
199
	osd_menu::menu_close_all
200
	set ::current_replay_slot $item
201
}
202
203
proc open_select_slot_menu {} {
204
	osd_menu::do_menu_open [menu_create_slot_menu]
205
	osd_menu::select_menu_item $::current_replay_slot
206
	for {set i 0} {$i < [llength [tas::list_slots]]} {incr i} {
207
		bind -layer slot_menu "$i" "tas::set_slot [lindex [tas::list_slots] $i]; tas::unbind_number_keys"
208
	}
209
	activate_input_layer slot_menu
210
}
211
212
proc unbind_number_keys {} {
213
	deactivate_input_layer slot_menu
214
}
215
216
proc save_replay_slot {} {
217
	reverse savereplay $::current_replay_slot
218
}
219
220
proc load_replay_slot {} {
221
	load_replay $::current_replay_slot
222
}
223
224
225
### Show Cursor Keys / 'fire buttons and others' ###
226
227
variable keys
228
229
proc show_keys {} {
230
	variable keys
231
232
	# get joysticka values
233
	set joy [debug read joystickports 0]
234
235
	foreach key $keys {
236
		show_key_press [dict get $key name] [eval [dict get $key check_expr]]
237
	}
238
}
239
240
#move to other TCL script?
241
proc is_key_pressed {row bit} {
242
	expr {!([debug read keymatrix $row] & (1 << $bit))}
243
}
244
245
proc show_key_press {key state} {
246
	set keycol [expr {$state ? 0xff000080 : "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80"}]
247
	osd configure cursors.$key -rgba $keycol
248
}
249
250
proc create_key {name x y} {
251
	osd create rectangle cursors.$name -x $x -y $y -w 16 -h 7 \
252
	-rgba "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80" \
253
	-bordersize 0.5 -borderrgba 0x00000040
254
	osd create text cursors.$name.text -x 2 -y 1 -text $name -size 4 -rgba 0xffffffff
255
}
256
257
# you can setup your own key definitions by loading a script which defines a proc like this:
258
#
259
# proc define_custom_keys {} {
260
#        variable tas::keys
261
#
262
#        set tas::keys {{
263
#                name up
264
#                x    20
265
#                y    2
266
#                check_expr {expr {([is_key_pressed 8 5] || !($joy &  1))}}
267
#        } {
268
#                name shift
269
#                x    186
270
#                y    8
271
#                check_expr {is_key_pressed 6 0}
272
#        }}
273
#}
274
#
275
276
proc toggle_cursors {} {
277
	variable keys
278
279
	if {[osd exists cursors]} {
280
		disable_periodic show_keys
281
		osd destroy cursors
282
	} else {
283
		osd create rectangle cursors -x 64 -y 219 -h 26 -w 204 -scaled true -rgba 0x00000000
284
		if {[info procs ::define_custom_keys] eq "::define_custom_keys"} {
285
			define_custom_keys
286
		} else {
287
			define_default_keys
288
		}
289
		foreach key $keys {
290
			create_key [dict get $key name] [dict get $key x] [dict get $key y]
291
		}
292
		enable_periodic show_keys
293
	}
294
}
295
296
proc define_default_keys {} {
297
	variable keys
298
299
	set keys {{
300
		name up
301
		x    20
302
		y    2
303
		check_expr {expr {([is_key_pressed 8 5] || !($joy &  1))}}
304
	} {
305
		name down
306
		x    20
307
		y    14
308
		check_expr {expr {([is_key_pressed 8 6] || !($joy &  2))}}
309
	} {
310
		name left
311
		x    2
312
		y    8
313
		check_expr {expr {([is_key_pressed 8 4] || !($joy &  4))}}
314
	} {
315
		name right
316
		x    38
317
		y    8
318
		check_expr {expr {([is_key_pressed 8 7] || !($joy &  8))}}
319
	} {
320
		name space
321
		x    60
322
		y    8
323
		check_expr {expr {([is_key_pressed 8 0] || !($joy & 16))}}
324
	} {
325
		name m
326
		x    78
327
		y    8
328
		check_expr {expr {([is_key_pressed 4 2] || !($joy & 32))}}
329
	} {
330
		name n
331
		x    96
332
		y    8
333
		check_expr {is_key_pressed 4 3}
334
	} {
335
		name z
336
		x    114
337
		y    8
338
		check_expr {is_key_pressed 5 7}
339
	} {
340
		name x
341
		x    132
342
		y    8
343
		check_expr {is_key_pressed 5 5}
344
	} {
345
		name graph
346
		x    150
347
		y    8
348
		check_expr {is_key_pressed 6 2}
349
	} {
350
		name ctrl
351
		x    168
352
		y    8
353
		check_expr {is_key_pressed 6 1}
354
	} {
355
		name shift
356
		x    186
357
		y    8
358
		check_expr {is_key_pressed 6 0}
359
	}}
360
}
361
362
363
### RAM Watch ###
364
365
# TODO:
366
# - be smarter with coordinates, by using negative ones
367
# - maybe put description on separate line to make window narrow again?
368
369
variable addr_watches [dict create]  ;# dict of RAM watches
370
371
# TODO move to utils?
372
proc dict_insert_sorted {dictname key value} {
373
	upvar $dictname d
374
	set i 0
375
	dict for {k v} $d {
376
		if {$key <= $k} {
377
			if {$key == $k} {
378
				dict set d $key $value
379
				return $d
380
			}
381
			break
382
		}
383
		incr i 2
384
	}
385
	set d [linsert $d $i $key $value]
386
}
387
388
proc ram_watch_add {addr_str args} {
389
	variable addr_watches
390
391
	set type_dict [dict create byte   {peek      2} b      {peek        2} \
392
	                           u8     {peek      2} s8     {peek_s8     2} \
393
	                           word   {peek16    4} w      {peek16      4} \
394
	                           u16    {peek16    4} s16    {peek_s16    4} \
395
	                           u16_LE {peek16    4} s16_LE {peek_s16    4} \
396
	                           u16_BE {peek16_BE 4} s16_BE {peek_s16_BE 4}]
397
	set format_dict [dict create dec "%d" hex "0x%0SX"]
398
399
	# sanitize input
400
	set addr [format 0x%04X $addr_str]
401
	if {($addr < 0) || ($addr > 0xffff)} {
402
		error "Address must be in range 0x0..0xffff, got: $addr_str"
403
	}
404
405
	set addr_already_watched [dict exists $addr_watches $addr]
406
407
	# defaults
408
	set desc "?"
409
	set type "byte"
410
	set format "hex"
411
	if {$addr_already_watched} {
412
		# start from the previously set values
413
		set desc   [dict get $addr_watches $addr -desc]
414
		set type   [dict get $addr_watches $addr -type]
415
		set format [dict get $addr_watches $addr -format]
416
	}
417
418
	while {[llength $args] > 0} {
419
		set option [lindex $args 0]
420
		switch -- $option {
421
			"-desc" {
422
				set desc [lindex $args 1]
423
				set args [lrange $args 2 end]
424
			}
425
			"-type" {
426
				set type [lindex $args 1]
427
				if {![dict exists $type_dict $type]} {
428
					error "Unsupported type: $type. Choose from: [dict keys $type_dict]"
429
				}
430
				set args [lrange $args 2 end]
431
			}
432
			"-format" {
433
				set format [lindex $args 1]
434
				if {![dict exists $format_dict $format]} {
435
					error "Unsupported format: $format. Choose from: [dict keys $format_dict]"
436
				}
437
				set args [lrange $args 2 end]
438
			}
439
			"default" {
440
				error "Invalid option: $option."
441
			}
442
		}
443
	}
444
445
	lassign [dict get $type_dict $type] peek_method num_hex_digits
446
	set fmtStr1 [dict get $format_dict $format]
447
	set fmtStr2 [string map [list S $num_hex_digits] $fmtStr1]
448
	set exprStr "set v \[$peek_method $addr\]; set r \"\[expr {(\$v < 0) ? \"-\" : \"\"}\]\[format $fmtStr2 \[expr {abs(\$v)}\]\]\"; set r"
449
450
	# add watch to watches
451
	set old_nof_watches [dict size $addr_watches]
452
	dict_insert_sorted addr_watches $addr [dict create -desc $desc -format $format -type $type exprStr $exprStr]
453
454
	# if OSD doesn't exist yet create it
455
	if {$old_nof_watches == 0} {
456
		ram_watch_init_widget
457
	}
458
459
	# (possibly) add one extra entry
460
	if {!$addr_already_watched} {
461
		ram_watch_add_to_widget $old_nof_watches
462
		ram_watch_update_widget_size
463
	}
464
465
	ram_watch_update_addresses
466
	if {$old_nof_watches == 0} {
467
		enable_periodic ram_watch_update_values
468
	}
469
	return ""
470
}
471
472
proc ram_watch_init_widget {} {
473
	osd create rectangle ram_watch \
474
		-x 0 -y 0 -h 240 -w 320 -scaled true -rgba 0x00000000
475
	osd create rectangle ram_watch.addr \
476
		-x 257 -y 1 -w 62 -h 221 \
477
		-rgba "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80" \
478
		-borderrgba 0x00000040 -bordersize 0.5
479
	osd create text ram_watch.addr.title -text "RAM Watch" -x 2 -y 1 -size 4 -rgba 0xffffffff
480
}
481
482
proc ram_watch_update_widget_size {} {
483
	variable addr_watches
484
	set nr [dict size $addr_watches]
485
	osd configure ram_watch.addr -h [expr {8 + ($nr * 6)}]
486
}
487
488
proc ram_watch_add_to_widget {nr} {
489
	osd create rectangle ram_watch.addr.mem$nr \
490
		-x 2 -y [expr {8 + ($nr * 6)}] -h 5 -w 16 -rgba 0x40404080
491
	osd create text  ram_watch.addr.mem$nr.text \
492
		-size 4 -rgba 0xffffffff
493
	osd create rectangle ram_watch.addr.val$nr \
494
		-x 19 -y [expr {8 + ($nr * 6)}] -h 5 -w 17 -rgba 0x40404080
495
	osd create text  ram_watch.addr.val$nr.text \
496
		-size 4 -rgba 0xffffffff
497
	osd create rectangle ram_watch.addr.desc$nr \
498
		-x 37 -y [expr {8 + ($nr * 6)}] -h 5 -w 23 -rgba 0x40404080 -clip true
499
	osd create text  ram_watch.addr.desc$nr.text \
500
		-size 4 -rgba 0xffffffff
501
}
502
503
proc ram_watch_remove {addr_str} {
504
	variable addr_watches
505
506
	# sanitize input
507
	set addr [format 0x%04X $addr_str]
508
509
	# check watch exists
510
	if {![dict exists $addr_watches $addr]} {
511
		# not an error
512
		return ""
513
	}
514
515
	#remove address
516
	dict unset addr_watches $addr
517
518
	set i [dict size $addr_watches]
519
520
	#remove one OSD entry
521
	osd destroy ram_watch.addr.mem$i
522
	osd destroy ram_watch.addr.val$i
523
	osd destroy ram_watch.addr.desc$i
524
525
	#if all elements are gone don't display anything anymore.
526
	if {$i == 0} {
527
		disable_periodic ram_watch_update_values
528
		osd destroy ram_watch
529
	} else {
530
		ram_watch_update_addresses
531
		ram_watch_update_widget_size
532
	}
533
	return ""
534
}
535
536
proc ram_watch_clear {} {
537
	variable addr_watches
538
	set addr_watches [dict create]
539
	disable_periodic ram_watch_update_values
540
	osd destroy ram_watch
541
	return ""
542
}
543
544
proc ram_watch_update_addresses {} {
545
	variable addr_watches
546
547
	set i 0
548
	dict for {addr v} $addr_watches {
549
		osd configure ram_watch.addr.mem$i.text -text $addr
550
		osd configure ram_watch.addr.desc$i.text -text [dict get $v -desc]
551
		incr i
552
	}
553
}
554
555
proc ram_watch_update_values {} {
556
	variable addr_watches
557
558
	set i 0
559
	dict for {addr v} $addr_watches {
560
		set exprStr [dict get $v exprStr]
561
		osd configure ram_watch.addr.val$i.text -text [eval $exprStr]
562
		incr i
563
	}
564
}
565
566
proc ram_watch_save {name} {
567
	variable addr_watches
568
569
	# exprStr property doesn't need to be saved
570
	set filtered_watches [dict create]
571
	dict for {addr v} $addr_watches {
572
		dict set filtered_watches $addr [dict filter $v key -*]
573
	}
574
575
	set directory [file normalize $::env(OPENMSX_USER_DATA)/../ramwatches]
576
	file mkdir $directory
577
	set fullname [file join $directory ${name}.wch]
578
579
	if {[catch {
580
		set the_file [open $fullname {WRONLY TRUNC CREAT}]
581
		puts $the_file $filtered_watches
582
		close $the_file
583
	} errorText]} {
584
		error "Failed to save to $fullname: $errorText"
585
	}
586
	return "Successfully saved RAM watch to $fullname"
587
}
588
589
proc ram_watch_load {name} {
590
	variable addr_watches
591
592
	set directory [file normalize $::env(OPENMSX_USER_DATA)/../ramwatches]
593
	set fullname [file join $directory ${name}.wch]
594
595
	if {[catch {
596
		set the_file [open $fullname {RDONLY}]
597
		set new_addr_watches [read $the_file]
598
		close $the_file
599
	} errorText]} {
600
		error "Failed to load from $fullname: $errorText"
601
	}
602
603
	ram_watch_clear
604
	dict for {addr v} $new_addr_watches {
605
		ram_watch_add $addr {*}$v
606
	}
607
	return "Successfully loaded $fullname"
608
}
609
610
proc list_ram_watch_files {} {
611
	set directory [file normalize $::env(OPENMSX_USER_DATA)/../ramwatches]
612
	set results [list]
613
	foreach f [lsort [glob -tails -directory $directory -type f -nocomplain *.wch]] {
614
		lappend results [file rootname $f]
615
	}
616
	return $results
617
}
618
619
proc ram_watch {subcmd args} {
620
	switch -- $subcmd {
621
		"add"    {ram_watch_add    {*}$args}
622
		"remove" {ram_watch_remove {*}$args}
623
		"clear"  {ram_watch_clear  {*}$args}
624
		"load"   {ram_watch_load   {*}$args}
625
		"save"   {ram_watch_save   {*}$args}
626
		default  {error "Invalid subcommand: $subcmd"}
627
	}
628
}
629
630
set_help_proc ram_watch [namespace code ram_watch_help]
631
proc ram_watch_help {args} {
632
	switch -- [lindex $args 1] {
633
		"add"    {return {Add an address to the list of RAM watch addresses on the right side of the screen. The list will be updated in real time, whenever a value changes.
634
635
Syntax: ram_watch add <address> [<options>...]
636
Possible options are:
637
    -desc <description> describes the address
638
    -type <type>        datatype: byte, word, u8, s8, u16, s16, u16_BE, ...
639
    -format <format>    formatting: dec, hex
640
}}
641
		"remove" {return {Remove an address from the list of RAM watch addresses from the list of RAM watch addresses on the right side of the screen. When the last address is removed, the list will disappear automatically.
642
643
Syntax: ram_watch remove <address>
644
}}
645
		"clear"  {return {Removes all RAM watches.
646
647
Syntax: ram_watch clear
648
}}
649
		"save"   {return {Save the current list of RAM watches to a file.
650
651
Syntax: ram_watch save <filename>
652
}}
653
		"load"   {return {Load a list of RAM watches from file.
654
655
Syntax: ram_watch load <filename>
656
}}
657
		default {return {Control the RAM watch functionality.
658
659
Syntax:  ram_watch <sub-command> [<arguments>]
660
Where sub-command is one of:
661
    add      Add (or modify) a new RAM watch address
662
    remove   Remove a previously added address
663
    clear    Shortcut to remove all addresses
664
    save     Save current list of RAM watches to a file
665
    load     Load a previously saved list of RAM watches
666
667
Use 'help ram_watch <sub-command>' to get more detailed help on a specific sub-command.
668
}}
669
	}
670
}
671
672
set_tabcompletion_proc ram_watch [namespace code ram_watch_tabcompletion]
673
proc ram_watch_tabcompletion {args} {
674
	if {[llength $args] == 2} {
675
		return [list "add" "remove" "clear" "save" "load"]
676
	}
677
	switch -- [lindex $args 1] {
678
		"add"   {return [list -desc -type -format]}
679
		"load"  -
680
		"save"  {return [list_ram_watch_files]}
681
		default {return [list]}
682
	}
683
}
684
685
### Lag counter ###
686
687
variable lag_counter
688
variable lag_counter_wp
689
variable previous_frame_count 0
690
variable input_read_in_frame false
691
692
proc space_read {} {
693
	variable input_read_in_frame
694
	set input_read_in_frame true
695
}
696
697
proc toggle_lag_counter {} {
698
	variable lag_counter
699
	variable lag_counter_wp
700
701
	if {[osd exists lag_counter]} {
702
		osd destroy lag_counter
703
		debug remove_watchpoint $lag_counter_wp
704
		return ""
705
	}
706
	# we set it to -1, because the updater is always called at the start
707
	# and there the incr is present, so it will always incr. We compensate
708
	# for that by setting it to -1. See also on other places where we
709
	# force a call to the updater.
710
	set lag_counter -1
711
712
	set lag_counter_wp [debug set_watchpoint read_io 0xa9 {[expr [debug read ioports 0xaa] & 0x0f] == 0x08} {tas::space_read}]
713
714
	osd create rectangle lag_counter \
715
		-x 269 -y 220 -h 7 -w 42 -scaled true \
716
		-borderrgba 0x00000040 -bordersize 0.5
717
	osd create text lag_counter.text -x 3 -y 1 -size 4 -rgba 0xffffffff
718
	update_lag_counter ;# can't use enable_periodic
719
	return ""
720
}
721
722
proc update_lag_counter {} {
723
	update_lag_counter2
724
	after frame [namespace code update_lag_counter]
725
}
726
727
proc update_lag_counter2 {} {
728
	variable lag_counter
729
	variable previous_frame_count
730
	variable input_read_in_frame
731
732
	if {![osd exists lag_counter]} return
733
734
	set new_frame_count [machine_info VDP_frame_count]
735
	if {$new_frame_count == 1} {
736
		# reset lag counter if frame counter is reset (e.g. after reset, or loading replay)
737
		set lag_counter -1
738
	}
739
	# GFX9000 also triggers frame ends, so we should check if we
740
	# actually advanced a V99x8 frame here
741
	# Also, we check whether it was reset to force updating the display
742
	if { $previous_frame_count != $new_frame_count || $lag_counter == -1} {
743
		set previous_frame_count $new_frame_count
744
745
		set col [expr {!$input_read_in_frame ? 0xff000080 : "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80"}]
746
		osd configure lag_counter -rgba $col
747
748
		if {!$input_read_in_frame} {
749
			incr lag_counter
750
			osd configure lag_counter.text -text "Lag fr.: $lag_counter"
751
		} else {
752
			set input_read_in_frame false
753
		}
754
	}
755
}
756
757
proc reset_lag_counter {} {
758
	variable lag_counter
759
	set lag_counter -1
760
	update_lag_counter2
761
	return ""
762
}
763
764
### movie length display ###
765
766
set_help_text toggle_movie_length_display\
767
{Toggles display of the movie length in the upper right corner.}
768
769
proc toggle_movie_length_display {} {
770
	if {[osd exists movielength]} {
771
		disable_periodic movielength_update
772
		osd destroy movielength
773
		return ""
774
	}
775
776
	osd create rectangle movielength \
777
		-x 269 -y 234 -h 7 -w 42 -scaled true \
778
		-rgba "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80" \
779
		-borderrgba 0x00000040 -bordersize 0.5
780
	osd create text movielength.text -x 3 -y 1 -size 4 -rgba 0xffffffff
781
	enable_periodic movielength_update
782
	return ""
783
}
784
785
proc movielength_update {} {
786
	osd configure movielength.text -text "Length: [utils::format_time_subseconds [dict get [reverse status] last_event]]"
787
}
788
789
namespace export toggle_frame_counter
790
namespace export prev_frame
791
namespace export next_frame
792
namespace export start_of_frame
793
namespace export advance_frame
794
namespace export reverse_frame
795
namespace export toggle_cursors
796
namespace export ram_watch
797
namespace export toggle_lag_counter
798
namespace export reset_lag_counter
799
namespace export toggle_movie_length_display
800
}
801
802
namespace import tas::*