Templot Club Archive 2007-2020                             

topic: 3221Templot program code
author remove search highlighting
 
posted: 21 Feb 2018 19:40

from:

Martin Wynne
 
West Of The Severn - United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
In this topic I am going to post some of the program code for Templot.

That's because there has been some interest in it, and the future of Templot. 

Please note this remains my copyright. It is not open source nor in the public domain. Not to be used in any application without my express permission. It is here purely for interest and comment.

This is the panning_unit. It contains the code for the zoom/pan dialog on the trackpad. It is largely unchanged since first written. It is written in Pascal, originally for Delphi2.

Because of line lengths it won't paste happily directly here, so I'm attaching it as a plain text file. It should open in any text editor or word processor. If you download a copy of Notepad++ (free), from:

  http://notepad-plus-plus.org/

you will be able to set the Language to Pascal and get the syntax colouring which makes it more readable.

p.s. Please note that any direct emails about this will be ignored. I just don't have the time, sorry. Comments here on Templot Club only please. 

cheers,

Martin.
Attachment: attach_2642_3221_panning_unit.txt     283

posted: 22 Feb 2018 00:11

from:

Jim Guthrie
 
United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
I haven't looked at Delphi code for over eight years. A quick peek and it all started to come back. :D:D

What coders might want to see is your code that generates the rail and sleeper lines. :D

Jim.

posted: 22 Feb 2018 00:35

from:

Martin Wynne
 
West Of The Severn - United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Jim Guthrie wrote:
What coders might want to see is your code that generates the rail and sleeper lines. :D
Hi Jim,

Yes, but that's thousands of lines of code. I thought I would start with something simpler. :)

I've just done a fresh build. Compiler says total 383,427 lines.

I don't know how that is calculated, but it works out at 52 lines per day for 20 years. :shock:

cheers,

Martin.

posted: 22 Feb 2018 00:59

from:

Rob Manchester
 
Manchester - United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Martin Wynne wrote:

I've just done a fresh build. Compiler says total 383,427 lines.

I don't know how that is calculated, but it works out at 52 lines per day for 20 years. :shock:

cheers,

Martin.
Come on Martin, Mark Twain is reputed to have averaged 1800 words a day, lots of your lines aren't even full ones and some of your words are repeated a lot :D

On the other hand he didn't have to write while consulting a slide rule calculator and a copy of British Railway Track !

Shame about the big tree, take some pics if you get out.

Rob

P.S. I hope Templot is available on cassette tape, wouldn't want to type 383,427 lines of code in from a magazine article.



posted: 22 Feb 2018 07:57

from:

Jim Guthrie
 
United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Martin Wynne wrote:
Jim Guthrie wrote:
What coders might want to see is your code that generates the rail and sleeper lines. :D
Hi Jim,

Yes, but that's thousands of lines of code. I thought I would start with something simpler. :)

I've just done a fresh build. Compiler says total 383,427 lines.

I don't know how that is calculated, but it works out at 52 lines per day for 20 years. :shock:

cheers,

Martin.

I can't remember how many lines there were in my application but it would have been a very small fraction of your line count.    I might see if I can find the source files somewhere on an old PC and check for interest. :D

As a matter of interest,  how quickly does your application compile.    One thing I do remember from my Delphi code writing days is that the Borland compiler was extremely fast and compile time was a second or two.  This tended to encourage lazy coding by allowing you to write a line or two,  then compile,  then write4 a line or two,  compile,  etc.  :D   With other compilers I had used,  you could make a cup of tea while waiting on a compile finishing. :D

Jim.

posted: 22 Feb 2018 09:13

from:

Mark Barry
 
Australia

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
It is amazing how verbose UI code can be.  I'm not surprised you haven't revisited it in years.

Its been quite some time since I looked at Pascal code - I'd forgotten the variable name: type syntax, so used to the C style of 'type' followed by variable name.

You should be congratulated on the easy to understand names too :thumb:.

Looking forward to the next installment.

Mark

posted: 22 Feb 2018 13:05

from:

Jim Guthrie
 
United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
I dragged out an old PC and managed to dig out the project files for my Delphi app.  Unfortunately,  I couldn't compile it since I had used a component called Hyperstring to do some of the string parsing in the app and I can't find it,  so I couldn't compile the project to see how many lines there were.  

Here's a pic of the app

25_220754_160000000.jpg25_220754_160000000.jpg

...so definitely not in the Templot league. :D   But it did a lot of conversion and generation calculations for film post production files using files from video transfer and binary files from hard disk recorders.   I reckon it probably allowed me to turn over in excess of £2M in twelve years of use,  so not bad for something written on a not very expensive compiler.   The compiler was installed on every PC that was used so that any bugs that cropped up during use could on debugged on the PC when they happened.   In the early days there could be two or three re-compiles some nights. :D

I've attached a file of the main processing unit which created objects of the input files then went through all the requisite processing.   Other units were the objects for each type of file and contained all the processing required to read and/or generate the types of files.

I almost feel tempted to try doing a bit of code again.    I upgraded to Delphi V10 a year or two ago when it was offered for free although I might stick with V7 which I think was probably one of the most stable versions.

Jim.
Attachment: attach_2643_3221_MalcPrc0501.txt     226
Last edited on 22 Feb 2018 13:15 by Jim Guthrie
posted: 22 Feb 2018 13:19

from:

Martin Wynne
 
West Of The Severn - United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Mark Barry wrote:
You should be congratulated on the easy to understand names too :thumb:.
Hi Mark,

Thanks. Welcome to Templot Club. :)

You may change your mind when you see some of the other code. :(

cheers,

Martin.

posted: 22 Feb 2018 13:58

from:

Martin Wynne
 
West Of The Severn - United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Jim Guthrie wrote:
As a matter of interest,  how quickly does your application compile. One thing I do remember from my Delphi code writing days is that the Borland compiler was extremely fast and compile time was a second or two.
Hi Jim,

Out of interest I just timed a full build from scratch -- took 9 seconds.

Normally of course if you do a Compile or Run, Delphi compiles only any units which have changed since the previous compile. For the rest it simply links the previously compiled DCU files.

So I just made a change in one small unit and clicked Run.

Result 2 seconds to see running Templot appear on screen. As you say, that's so fast that you can do a test Run after most of the smaller code changes.

I still remember slightly longer compilation times -- drive 20 miles through heavy traffic with several trays of punched cards in the back of the car to be handed in by 5pm. Wait for a phone call next morning, while setting up a chain-welding machine, to see if they managed to run the job overnight (there were always hold-ups), and if it was successful. :)

Thanks for posting your code. I see you were sticking to the conventional Delphi style of camel case and spaces around operators. I couldn't get on with either of those, or with not having the thens and elses equally indented. Likewise begins and ends. I soon developed my own style, and have stuck with it unchanged for all these years. Camel case for Delphi's own identifiers, and lower case with underscores for my own. A useful distinction:

if x>0
   then begin
          ClientHeight:=client_height;  
          pad_canvas.Pen.Width:=pen_width;
        end
   else begin
              ....
        end;


Which I find much more readable than most published code. But of course I'm an oily fingered toolmaker, not a software developer. They objected to oily finger marks on the punched cards, I remember. :)

cheers,

Martin.

posted: 22 Feb 2018 16:38

from:

Nigel Brown
 
 

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Martin Wynne wrote:

I still remember slightly longer compilation times -- drive 20 miles through heavy traffic with several trays of punched cards in the back of the car to be handed in by 5pm. Wait for a phone call next morning, while setting up a chain-welding machine, to see if they managed to run the job overnight (there were always hold-ups), and if it was successful. :)

Ah, the good old days :D

We ran a fast timetabled batch service for small jobs; hand in the job by X and guaranteed returned by X+1. Large jobs had to wait for the evening run. It was actually highly efficient, the system having been tuned for that purpose.

Nigel

posted: 22 Feb 2018 16:46

from:

Jim Guthrie
 
United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Martin Wynne wrote:
Thanks for posting your code. I see you were sticking to the conventional Delphi style of camel case and spaces around operators. I couldn't get on with either of those, or with not having the thens and elses equally indented. Likewise begins and ends. I soon developed my own style, and have stuck with it unchanged for all these years. Camel case for Delphi's own identifiers, and lower case with underscores for my own. A useful distinction:
I stuck with the Delphi style and fairly verbose naming principally because there were times when I was debugging code up against tight deadlines and I didn't want to use other styles which might have been OK at the time,  but didn't help months later when I had forgotten about them.  Our work normally came in about 1am and if a problem cropped up in use,  I had to debug and re-write code to get the work on the van at 7am.  So the last thing I wanted was "what the hell did I do there" moments. :D

The worst night was when we got our first NTSC job and no one warned us beforehand and I had to write a complete procedure to deal with it.   The main problem with NTSC is that the Americans saddled us with a 29,97 frame rate which is an absolute bu**er to deal with.   I got the procedure working just in time but had to go back in the late morning to tidy it all up and make it presentable for the future. :D

Jim.

posted: 22 Feb 2018 22:18

from:

Martin Wynne
 
West Of The Severn - United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Jim Guthrie wrote:
What coders might want to see is your code that generates the rail and sleeper lines. :D
Hi Jim,

OK, here is some of that. First a bit of explanation.

The core turnout calculations take place on a left-hand straight turnout:

2_221547_480000000.png2_221547_480000000.png

All the dimensions for this are global variables calculated by a routine called gocalc (the generator).

This is called many, many times while Templot is running. For example if you are adjusting the crossing angle with F9, it will be called every time Windows reports the mouse having moved one or more pixels on the screen.

The variables are global so that they can be used anywhere in Templot if needed. For example the variable bnx is the x dimension to the blunt nose, g is the track gauge, etc. All the dimensions are in mm to extended precision (80-bit, 10-byte).

After being curved to the required main road radius and transformed (shifted/rotated) to its proper position on the trackpad grid, this template gets drawn as the control template.

The dimensions are referenced from the template datum, which is the gauge-face of the straight stock rail, at CTRL-0. This template datum is marked on the trackpad as a green dot.

All the rail edges and centre-lines which make up the template have an aq number. The code attached below relates to the turnout-road centre-line, for which aq=25. There are a lot of these 2-character variable names in Templot. They all date back to the very first micro-processor implementation in 1980, where the compiler was limited to 2-character names. I can't remember why I chose aq for this, but it is a core variable in Templot.

There are two functions in the attached code.

aq25offset returns the turnout-road offset and turn angle k at any xs  from the datum (see diagram above). This function is called often, not only to generate the line, but also for the peg calculations for the various tools functions.

turnroad_cl is called from the generator to create a series of co-ordinates along the turnout-road centre-line, and put them in an array ready for the actual screen drawing function, or printing, or whatever. This routine calls aq25offset to create each co-ordinate pair. Note that it doesn't need the angle k for this purpose.

A few notes about the code.

function aq25offset(xs:extended; var k:extended):extended;   

That's a funny way of doing it. The offset is returned as a normal function result, but the angle is put in a var parameter supplied by the calling routine.

After all these years I honestly can't remember why I did it that way. It would be more usual to return a record type containing both values, or else use var parameters for both.

But it works fine, and I tend to leave things alone if they are working.

As you can see there are some local procedures within this function, which are heavily indented so that I don't get confused.

You will notice a strange construct which I use often:

repeat ... until 0<>0;

An infinite loop! But with BREAK statements to escape from it. Sometimes before it ever loops back. I find this much more readable than a series of nested else if statements, much easier to follow the logic, and much easier to modify without making errors. And much more flexible than a case statement.

Notice also the function name f28000. This again goes back to the 1980 compiler which had numbered lines. I prefixed them with f to make them valid function names in Pascal. There are several others similar. This function does the curving and transform calculations, and then puts the co-ordinates in the output array for aq index 25.

Here's the code attached.

cheers,

Martin.
Attachment: attach_2644_3221_aq25.txt     212

posted: 23 Feb 2018 09:10

from:

Mark Barry
 
Australia

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Martin

Firstly thanks for giving us the opportunity to glance into the inner workings, and a trip down memory lane - the compiler limitation of short variable names raised a smile.  These days you need to be a better typist due to the length of the variable names (thank goodness for tab complete!).

I think there may be a number of opportunities to improve performance of some of the computations.  It all depends on the frequency these are called - but you mentioned that was  'many many times', so maybe :).  

Some compilers can do these things for you, but others aren't that clever or are at least more conservative hence don't.  That can be especially true when dealing with 'global variables'.

In the HPC (High Performance Computing) arena optimizing computational code is really important (spent almost 20 years in this space).  As computers are generally seen as 'fast', hence most mathematical operations are viewed equally, i.e. adds, subtractions, multiplications and divisions.  However that is not the case at all!  Divisions are much much slower than the others - hence should be used with care (when called millions of times).  I think it may be even more critical with your 'extended' 80bit precision representations.

There are several places in aq25offset() which have 'divide by constant' calculations.  These can be turned in 'multiply by inverse'.  The idea is to create 'local constants' that are calculated once and used often.

For example,
(line 83)  k:=ARCSIN(x/rto);                    // and gradient angle.
 
The value of rto assigned at line 51 (within do_turnout_curve()),  rto:=tradius-g/2;  
      
Now, tradius is a gobal variable, as is g and neither of these is altered by turnroad_cl() or aq25offset() hence we can consider rto to be a local constant.  
 
Then 'rto' is used in: ABS(rto), SQR(rto), SGZ(rto) [what does SGZ() calculate - I didn't recognise that one, and Google wasn't much help!], and ARCSIN(x/rto). If these are called frequently enough, it would be worth using 4 variables to hold the calculated values of each.  At minimum, the 1/rto used in the ARCSIN calculation should be done once and then used in each call to do_turnout_curve(), e.g ARCSIN(x * inv_rto) where inv_rto = 1/rto and done once at the start of aq25offset().
    
'g/2' is another good candidate, and 'Pi/2' is a definite starter. Whilst 'divide by 2' can be reduced to a bit shift operation hence is very fast - is that happening?  Not sure with your 80bit floating point data type.
 
Your 'repeat ... until 0<>0;' is definitely an 'interesting' way to express the 'nested if' logic :shock:.  If it works, it works - would certainly help to demonstrate that it is your code.  I've never seen anyone else use it. :thumb:

I'd normally use a 'profiler' to gather some real stats and see which operations are the real targets for optimising.  There is little point in worrying about things that only take a few tenths of a second (or less).  That may be the case with my observations above - on the other hand they may make a big difference in certain circumstances.  I'm thinking of some recent posts about performance whilst working plans with a large number of templates and screen 'redraw' times.
 
Thanks again for sharing.

Mark

posted: 23 Feb 2018 13:25

from:

Martin Wynne
 
West Of The Severn - United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Hi Mark,

Thanks for your insights. :)

You are not saying anything which is new to me. I have tried numerous optimizations over the years and running profiler programs.

None of it ever made more than a negligible difference to the performance, because *95% of the processor time is taken up with the Windows GDI screen drawing functions. The only way to make any significant difference to Templot performance is to reduce the amount data sent to the GDI. For example if you select the generator > use skeleton settings option, you will see a noticeable improvement.

I don't doubt there is a lot more I could do in that direction by delving deeper into the GDI functions, or by using a different graphics library. The sketchboard engine does already use the Graphics32 library instead of the Windows GDI, see:

  http://en.wikipedia.org/wiki/Graphics32

It is very tempting to try switching all the trackpad drawing to Graphics32. One day maybe. There is only so much I can do without Templot taking up my entire life.

So I don't see much to be gained by optimizing the maths routines to save a few microseconds here and there. I like to write it in a way that will be as easily readable as possible when I come back to it years later. My grey cells are dying fast. :(

For example Pi/2 radians is the standard mathematical way to refer to 90 degrees, so that's what I write rather than a pre-defined constant. I know an unnecessary division is time consuming, but it doesn't seem to make a measurable difference. Dividing by 2 should involve only decrementing the exponent part.

Likewise g/2 for the offset to the track centre-line. That occurs in so many places in Templot it must add up a bit, perhaps I will try replacing it with a global hg (half-g) and see how much difference it does make.

For the same reason I like to write if gaunt=True rather than if gaunt. Both are equally valid, but the first seems to emphasize the relevant condition when reading through what follows. I especially dislike using if not in conditionals because it just doesn't register when reading it later, compared with if gaunt=False. That may just be my brain of course. Users of Templot often refer to its strange nature.  :)

SGZ returns +1 for zero.
SGN returns 0 for zero.

*guesstimate

cheers,

Martin.

posted: 24 Feb 2018 00:26

from:

Mark Barry
 
Australia

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Martin Wynne wrote:
Hi Mark,

Thanks for your insights. :)

You are not saying anything which is new to me. I have tried numerous optimizations over the years and running profiler programs.

None of it ever made more than a negligible difference to the performance, because *95% of the processor time is taken up with the Windows GDI screen drawing functions. The only way to make any significant difference to Templot performance is to reduce the amount data sent to the GDI. For example if you select the generator > use skeleton settings option, you will see a noticeable improvement.

...
Martin

Agreed, not much point in chasing speed performance improvements in less than 5% of the processor time.  Also, having the logic more explicitly stated also makes a lot of sense, code after all is the human readable form - the compiler can 'simplify' things.

Now that your allowed more than two character variable names, you could lash out with Pi_on_2 :D, three extra characters to type over 'Pi/2' (and reads the same in my head).

My HPC experience has been more with 'batch' computational codes that run for hours/days/weeks rather than 'interactive' applications.  In that world considerable speedups are possible applying this approach due to shear number of times a function could be called. 

Thanks again for sharing the code.

Mark


posted: 24 Feb 2018 01:30

from:

Martin Wynne
 
West Of The Severn - United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Mark Barry wrote:
you could lash out with Pi_on_2 :D, three extra characters to type over 'Pi/2' (and reads the same in my head).
Hi Mark,

You have certainly got me thinking about this stuff. Unfortunately Pi_on_2 or Pi_over_2 doesn't read the same in my head. When I read Pi/2 I can mentally see a right-angle, if that makes sense?

In any event it shouldn't be Pi with an upper case P because Pi is a reserved constant in Delphi. I could maybe live with half_pi which is only 3 extra characters and reads ok to me. Also half a pie is better for me. :)

Actually the number of characters doesn't really matter, it would be a search and replace operation.

g/2 is possibly hg, although that reads as Mercury of course. :?

I do have scale as mm/ft and inscale as mm/inch. Otherwise I would be constantly writing scale/12.

cheers,

Martin.

posted: 11 Apr 2018 15:05

from:

Andy Vines
 
Market Harborough - United Kingdom

click the date to link to this post
click member name to view archived images
view images in gallery view images as slides
Always assumed Templot was written in Delphi/Pascal from the Icon you use.

I started with Delphi 2, upgraded through to Delphi 7, then stuck with that until XE4, I now use Delphi 7 & XE4 to maintain my projects and will not be upgrading again due to the direction the upgrade model has gone (too expensive for part time developer)

Would probably use Lazarus now if I started any personal or opensource projects.



Templot Club > Forums > Templot talk > Templot program code
about Templot Club

Templot Companion - User Guide - A-Z Index Templot Explained for beginners Please click: important information for new members and first-time visitors.
indexing link for search engines

back to top of page


Please read this important note about copyright: Unless stated otherwise, all the files submitted to this web site are copyright and the property of the respective contributor. You are welcome to use them for your own personal non-commercial purposes, and in your messages on this web site. If you want to publish any of this material elsewhere or use it commercially, you must first obtain the owner's permission to do so.
The small print: All material submitted to this web site is the responsibility of the respective contributor. By submitting material to this web site you acknowledge that you accept full responsibility for the material submitted. The owner of this web site is not responsible for any content displayed here other than his own contributions. The owner of this web site may edit, modify or remove any content at any time without giving notice or reason. Problems with this web site? Contact webmaster@templot.com.   This web site uses cookies: click for information.  
© 2020  

Powered by UltraBB - © 2009 Data 1 Systems