SRL Archive - OSR Help

Access Violations in GUI


MrMonotone

2015-02-20 01:56:13

The following code throws an access violation at 106. I have tried doing some debug which can be seen starting at line 97. However I do not understand the error.
Here is the code in question.
Thanks for your time
MM

program new;
{$DEFINE SMART}
{$i AeroLib/AeroLib.Simba}
{$i Reflection/Reflection.simba}
type
Key = (Cooking, Fletching, Fishing, Firemaking, Woodcutting);
Choosen = set of Key;
Skill = Procedure;

TPair = record
K: Key;
V: Skill;
end;

TMap = array of TPair;
const
FULL_PATH = scriptPath + 'TestConfig.ini';
var
myForm: TForm;
myCheckList: TCheckListBox;
myTabControl: TPageControl;
myTabs: array [0..5] of TTabSheet;
myUserBox, MyPassBox, MyPinBox: TMemo;
myStartButton: TButton;
myTaskLabel, myUserLabel, myPassLabel, myPinLabel : TLabel;
myMainMenu: TMainMenu;
myMenuFile, mySubMenuSave, mySubMenuLoad: TMenuItem;

MyPlayer: TReflectLocalPlayer;
MyLogger: TReflectLogger;


function TMap.Add(K: Key; V: Skill): boolean;
var
I: Integer;
begin
for I := 0 to High(Self) do
if (Self[I].K = K) then
Exit(False);

SetLength(Self, Length(Self) + 1);
Self[High(Self)].K := K;
Self[High(Self)].V := @V;
exit(True);
end;

function TMap.Remove(K: Key): boolean;
var
I: Integer;
begin
if (Self.get(K) = nil) then
Exit(False);

for I := 0 to High(Self) - 1 do
if (Self[I].K = K) then
Swap(Self[I], Self[I + 1]);
SetLength(Self, Length(Self) - 1);
Exit(True);
end;

function TMap.Get(K: Key): Skill;
var
I: Integer;
begin
for I := 0 to High(Self) do
if (Self[I].K = K) then
Exit(@Self[I].V);
Exit(nil);
end;

procedure OnClickCheckList(sender: TObject; index: Integer);
{$IFNDEF CODEINSIGHT} native; {$ENDIF}
begin
if myCheckList.getChecked(index) then
begin
myTabs[index] := myTabControl.addTabSheet();
myTabs[index].setCaption(myCheckList.getItems().getStrings(index));
end
else
begin
myTabs[index].Free;
end;
end;

procedure onClick(sender: TObject);
{$IFNDEF CODEINSIGHT} native; {$ENDIF}
var
I : Integer;
begin
case sender of
myStartButton:
begin
myForm.Free;
end;
mySubMenuSave:
begin
//Client.Writeln(myCheckList.getItems().getStrings(1));
//Client.Writeln( BoolToStr(myCheckList.getChecked(1)) );
//Client.WriteLn(FULL_PATH);
for I:= 0 to myCheckList.getItems.getCount - 1 do
begin
Client.Writeln('In iteration ' + IntToStr(I));
Client.Writeln(' ' + myCheckList.getItems().getStrings(I));
Client.Writeln(' ' + BoolToStr(myCheckList.getChecked(I)));
Client.Writeln('Does ' + FULL_PATH + ' exist? ' + BoolToStr(FileExists(FULL_PATH)));
WriteINI('Tasks', myCheckList.getItems().getStrings(I), BoolToStr(myCheckList.getChecked(I)), FULL_PATH);
end;
end;
mySubMenuLoad:
begin
end;
end;
end;

procedure InitGUI;
{$IFNDEF CODEINSIGHT} native; {$ENDIF}
var
I : Integer;
K : Key;
begin
with myForm do
begin
Init(nil);
SetCaption('Edgeville Extravaganza');
setBounds(0, 0, 400, 170);
setPosition(poScreenCenter);
end;

myMainMenu.Init(myForm);
myMenuFile := myMainMenu.AddMenu('File');
mySubMenuSave := myMenuFile.addMenu('Save');
mySubMenuLoad := myMenuFile.addMenu('Load');
mySubMenuSave.setOnClick(onClick);
mySubMenuLoad.setOnClick(onClick);

with myTabControl do
begin
Init(myForm);
setParent(myForm);
setBoundsRect(myForm.getBaseBounds);
myTabs[0] := addTabSheet();
myTabs[0].setCaption('Main');
end;

with myTaskLabel do
begin
Init(myForm);
setParent(myTabs[0]);
setCaption('Select Tasks:');
setLeft(5);
setTop(5);
end;

with myCheckList do
begin
Init(myForm);
setParent(myTabs[0]);
setLeft(myTaskLabel.getLeft);
setTop(myTaskLabel.getTop + myTaskLabel.getHeight + 5);
setHeight(95);
for K := Low(Key) to High(Key) do
getItems().add(ToStr(k));
setOnItemClick(OnClickCheckList);
end;

with myUserLabel do
begin
Init(myForm);
setParent(myTabs[0]);
setCaption('Enter User Info:');
setLeft(myCheckList.getLeft + myCheckList.getWidth + 10);
setTop(myTaskLabel.getTop);
end;

with myUserBox do
begin
Init(myForm);
setParent(myTabs[0]);
setText('Username or EMail');
setLeft(myUserLabel.getLeft);
setTop(myUserLabel.getTop + myUserLabel.getHeight + 5);
setWidth(270);
setHeight(25);
end;

with myPassLabel do
begin
Init(myForm);
setParent(myTabs[0]);
setCaption('Password:');
setLeft(myUserBox.getLeft);
setTop(myUserBox.getTop + myUserBox.getHeight);
end;

with MyPassBox do
begin
Init(myForm);
setParent(myTabs[0]);
setText('Password');
setLeft(myPassLabel.getLeft);
setTop(myPassLabel.getTop + myPassLabel.getHeight);
setWidth(150);
setHeight(25);
setPasswordChar('*');
setMaxLength(20);
end;

with myPinLabel do
begin
Init(myForm);
setParent(myTabs[0]);
setCaption('Pin:');
setLeft(myUserBox.getLeft + myUserBox.getWidth - getWidth - 15);
setTop(myUserBox.getTop + myUserBox.getHeight);
end;

with MyPinBox do
begin
Init(myForm);
setParent(myTabs[0]);
setWidth(80);
setHeight(25);
setLeft(myPinLabel.getLeft);
setTop(MyPassBox.getTop);
setMaxLength(4);
setPasswordChar('*');
end;

with MyStartButton do
begin
Init(myForm);
setParent(myTabs[0]);
setCaption('Start');
setLeft(MyPinBox.getLeft);
setTop(MyPinBox.getTop + MyPinBox.getHeight + 5);
end;

myForm.ShowModal;
end;

procedure FreeGUI;
{$IFNDEF CODEINSIGHT} native; {$ENDIF}
begin
if (myForm = nil) then
exit;
MyLogger.Status('Freeing GUI...');
myForm.free;
end;

procedure StartGUI;
begin
try
sync(InitGUI);
except
MyLogger.Error('Failed to initialize GUI!');
finally
sync(FreeGUI);
end;
end;

procedure Cook;
begin
Writeln('Cook!');
end;

procedure Fletch;
begin
Writeln('Fletch!');
end;

procedure Fish;
begin
Writeln('Fish!');
end;

procedure Burn;
begin
Writeln('Burn!');
end;

procedure Chop;
begin
Writeln('Chop!');
end;

function GearInInv() : Boolean;
begin
Result:= true;
end;

function GearInEquip() : Boolean;
begin
Result:= true;
end;

function HasGear() : Boolean;
begin
Result:= GearInEquip OR GearInInv;
end;

var
Perform: TMap;
begin
StartGUI();
{Perform.add(Cooking, @cook);
if (Perform.get(Cooking) <> nil) then //check if it exists..
Perform.get(Cooking)(); //call the function.
Perform.get(Firemaking)();
//writeln();

Perform.remove(Cooking);
writeln(Perform.get(Cooking)); }
end.

MrMonotone

2015-02-22 23:51:20

Just for others who look in the future. According to @Olly , WriteINI is not thread safe meaning that it can not be used while the GUI is open. In order to save content from your GUI you must save them in strings then save them when the GUI is closed. This is just how LAPE is and will require a rewrite of LAPE for nicer solutions.

Brandon

2015-02-23 06:35:28

From: MrMonotone
Just for others who look in the future. According to @Olly , WriteINI is not thread safe meaning that it can not be used while the GUI is open. In order to save content from your GUI you must save them in strings then save them when the GUI is closed. This is just how LAPE is and will require a rewrite of LAPE for nicer solutions.


That's actually quite sad.. A bad excuse IMO.

If your GUI is the ONLY thread writing to that file, how exactly is it unsafe? It doesn't allocate any thread local storage variables.. Are you writing to the file from the GUI thread and main thread at the same time? If not, then you don't need to lock the file and there's nothing inherently unsafe about it. The only way it could be unsafe is if someone made it unsafe. Even with a mutex locking the file and an atomic variable guarding, it still crashes. This is due to bad code.

It doesn't make any sense for it to be "inherently unsafe" :S


Use:


Client.getMFiles().WriteINI('Tasks', myCheckList.getItems().getStrings(I), BoolToStr(myCheckList.getChecked(I)), FULL_PATH);

//or:

getTClient().getMFiles().WriteINI('Tasks', myCheckList.getItems().getStrings(I), BoolToStr(myCheckList.getChecked(I)), FULL_PATH);

//both will work.

as a work around to avoid all the thread non-sense and bad design/code. It doesn't require a rewrite for a "nice" solution in this case. I'm sorry you have to "work around" something like file writing. I really am.

Anyway, let me know if this solution works for you.

P.S. You have a nice GUI (I like the tabs on checkmark).

Olly

2015-02-23 11:01:42

Maybe "not thread safe" is a bad way to explain it, but like any wrapper (DTM, Bitmap, Files, Finder, etc) in Simba it's accessed through current thread variable.
CurrThread.Client.MFiles.ReadINI
if CurrThread is Simba's mainthread none of that exists.. so crash crash crash. (Not that Brandon doesn't know this)

There are multiple ways to do this a better way, personally I would like to add all the wrappers via AddDelayedCode though I have a feeling that would impact compiling time alot.

Also I didn't know INI writing/reading was part of TMFiles, which is why I never suggested it. :(

Brandon

2015-02-23 19:10:28

From: Olly
Maybe "not thread safe" is a bad way to explain it, but like any wrapper (DTM, Bitmap, Files, Finder, etc) in Simba it's accessed through current thread variable.
CurrThread.Client.MFiles.ReadINI
if CurrThread is Simba's mainthread none of that exists.. so crash crash crash. (Not that Brandon doesn't know this)

There are multiple ways to do this a better way, personally I would like to add all the wrappers via AddDelayedCode though I have a feeling that would impact compiling time alot.

Also I didn't know INI writing/reading was part of TMFiles, which is why I never suggested it. :(



And no one blames you (or anyone else) for that. It's just a bad design decision that has been around too long and it's finally catching up to "us" (everyone).

The decision to make file reading and a lot of things "dependent" on the current thread was a bad idea. It "used to be" a great idea back then and it was good enough back then, but Simba's usage + modern technology is slowly killing it.


File reading and writing is one of the core concepts of any programming language. The fact that we have to work around it is what makes this a sad situation.

No worries though. I had no idea it was part of TMFiles either. I had to go through the source to find it. Anyway, teach me how to export things to Simba and show me where writeln is exported (how does it accept infinite comma separated arguments without being variadic)?

Olly

2015-02-23 19:23:51

From: Brandon

No worries though. I had no idea it was part of TMFiles either. I had to go through the source to find it. Anyway, teach me how to export things to Simba and show me where writeln is exported (how does it accept infinite comma separated arguments without being variadic)?


Lape man, I don't think anyone actually knows other than niels lol. There is no 'AddGlobalFuncWithAnyParam' it's all 'Internal' methods, and I think they are actually created at compile time.

Here are where the internals are added, and the actual 'creating' but I don't know much more.

https://github.com/SRL/Lape/blob/master/lpcompiler.pas#L2895
https://github.com/SRL/Lape/blob/cd8a7f99d9508161814074089fb4ec90e0bbb1b3/lptree.pas#L2306

Tho…. New auto complete. !

">

MrMonotone

2015-02-25 01:26:41

Thanks @Olly @Brandon